Mister Epic
Mister Epic

Reputation: 16743

Injecting logger into middleware dependency

I have a middleware library I intend on using in multiple projects. The middleware itself looks something like:

public SdkMiddleware(RequestDelegate next, ILogger<SdkMiddleware> logger, ISdk sdk)
{
   this.next = next;
   this.logger = logger;
   this.sdk = agentSdk;

   this.sdk.Init();
   ...
}

Thanks to DI, I can simply inject my logger:

// Would rather this class be internal...
public class Sdk: ISdk
{
    private ILogger<Sdk> logger;
    public Sdk(ILogger<Sdk> logger)
    {
        this.logger = logger;
    }

    public void Init() {
        this.logger.Info(...) // Do some logging
    }

The downside to this is my class needs to be registered in every ASP.Net project's Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<ISdk, Sdk>();

Is the best/only route? Every time I want to inject a logger into a class, I need to register that class for DI in my composition root?

Upvotes: 2

Views: 4151

Answers (2)

aunoum
aunoum

Reputation: 171

There are a few things that I feel need clarification here:

Dependency injection/inversion of control

To understand what is the benefit of dependency injection(DI) it is better to look at the principle of inversion of control(IoT) that DI implements. In your case you want SdkMiddleware class to contain a reference to ILogger implementation. The simplest way to do this is for SdkMiddleware class to create an instance of a class that implements ILogger interface. The downside of such approach is that SdkMiddleware needs to know which class implements ILogger interface and how to instantiate it. In other words, SdkMiddleware has control over the creation of ILogger object. The inversion of control happens when the responsibility of knowing which class implements ILogger and how to create an instance of it is taken away from SdkMiddleware to some other class (dependency container in DI) and the instance if it is given to SdkMiddleware to use (through injection in DI). In this case the control over the creation of ILogger object is outside of SdkMiddleware. Since this changes the direction of control, it is called "Inversion of control". The benefit of this approach is when you will need to provide another implementation of ILogger or change the way you create an instance of that class, you don't need to change SdkMiddleware at all.

Bootstrapping

So now that we clarified why we are using DI, lets take a look at what do we need to actually use it. The object that creates all instances, controls which object is injected into which and gives away ready to use objects is usually called DI container or IoT container. In asp.net core "IServiceCollection services" is used as such a container. But it still needs to know how to create objects and which implementation to inject for which interface. This is where bootstrapping comes in. In a bootstrapper class or method you need to specify how objects are built from classes and how classes relate to interfaces. As an example of the former, imagine that you need to pass a connection string for a database from your configuration to a class that creates a db connection. As for the latter, that is exactly what your line "services.AddTransient()" does.

The answer

I am sorry it took so long to get to the actual answer for your question but I wanted to provide some overview first. Do you need to specify a relation between a class and an interface to inject logger into your class? No. Your class may not even have an interface to begin with and DI container will inject all the dependencies in it by default if you ask for an object of a class instead of an instance of an interface. You can also use or define some convention over configuration solution so that binding of classes and interfaces will happen automatically. The bottom line is that registration of a class and the actual injection are not connected. But the code you provided is the default way to do this.

Upvotes: 2

Darin Dimitrov
Darin Dimitrov

Reputation: 1039518

There is nothing wrong in having the consumer of your library compose the dependencies for this library in the composition root. That's how dependency injection works. You could of course provide some default implementations and a custom extension method that would register those default implementations into the DI and then let the consumer simply call your extension method.

Upvotes: 2

Related Questions