Passing Services using Dependency Injection and Factory Pattern in ASP.NET
There are a couple of errors in your approach:
- Your services depend on the concrete
LoggerFactory
type which is a Dependency Inversion Principle violation. - Doing this extra initialization can make building the object graph unreliable, while injection constructors should be simple.
- It hides the fact that the
ILogger
is the real service that your consumer depends upon. This makes the system harder to test, harder to maintain, and complicates object graph analysis. - The use of a factory is a smell, since factories are hardly ever the right solution.
Instead, your service should look as follows:
public class MyService
{
private ILogger _logger;
public MyService(ILogger logger)
{
_logger = logger;
}
}
This dramatically simplifies all consumers that depend upon ILogger
. This also means that getting the right ILogger
for MyService
becomes a responsibility of the Composition Root, which is the correct place to have this knowledge.
It does mean however that you might need to move away from the built-in DI container of ASP.NET Core to a more feature rich DI library, because the built-in container is not capable of making a context aware registration for ILogger
while having the library auto-wire other constructor dependencies as well.
With the ASP.NET Core DI container, you can only hand-wire your services using a delegate. For instance:
services.AddTransient<MyService>(c => new MyService(
BuildLogger(typeof(MyService).Name),
c.GetRequiredService<ISomeOtherDependency>(),
c.GetRequiredService<IYetAnotherOne>());
Hussein Salman
Engineering Manager & Cloud-Native Architect, focusing on Kubernetes, Containers & Microservices. Check out my youtube channel: https://www.youtube.com/channel/UCoAh8g6dmwXQUwKhkggUFIA
Updated on June 08, 2022Comments
-
Hussein Salman almost 2 years
I am using ASP.NET Core, I know that such Logging mechanism is already provided by the framework, but using this to illustrate my problem.
I am using kind of Factory pattern to build the Logger class, since I don't know the type of logging (because it is stored in DB).
The ILogger Contract
Log(string msg)
Then LoggerFactory will return an ILogger after creating a Logger based on param passed from DB:
public class LoggerFactory { public static Contracts.ILogger BuildLogger(LogType type) { return GetLogger(type); } //other code is omitted, GetLogger will return an implementation of the related logger
Now, when I need to use the Logger I have to do it in this way:
public class MyService { private ILogger _logger public MyService() { _logger = LoggerFactory.BuildLogger("myType"); }
But, I intend to keep my classes without any instantiation, I need to use Constructor DI in MyService and I need to inject all the dependencies on Startup:
services.AddTransient<Contracts.ILogger, LoggerFactory.BuildLogger("param") > ();
But this will not work this we need to pass a concrete implementation. How to make that work using DI, is there a better approach for implementing that?