Dependency Injection in ASP.NET

Dependency Injection (DI) is one of those more intermediate to advanced level topics but something that you get out of the box with recent versions of ASP.NET. The framework makes it available to everyone with very little friction so that developers, even junior ones, can set themselves up for success in the long run. In this installment, let’s go over the basics of what DI is, why we want to use it and how to go about using it.

What is Dependency Injection (DI)?

Dependency Injection (DI) is not a concept that’s unique to .NET but exists in several other frameworks. At its most basic level, this is the practice of decoupling the dependencies in your code by having something else (a Dependency Injection Container for instance) provide/inject your dependencies from the outside. Usually this is done through constructor injection – specified as a parameter of a constructor; this is the case with the default dependency injection container that comes with ASP.NET. There are other frameworks where dependencies are injected through a setter.

What’s the Benefit of Using DI?

Take this snippet of code for instance that does not use dependency injection:

public class Example
{
    public Example()
    {
        // instantiate Email Sender service class
        var emailSender = new EmailSender();
        emailSender.SendEmail("tom@test.net");
    }

}

public class EmailSender
{
    public bool SendEmail(string email)
    {
        // send email here.
        return true;
    }
}:

In the snippet above, Example class has a dependency on an EmailSender class. These two classes are coupled together. In a large code-base, there may be several other classes that may end up taking a dependency on this class. This type of coupling makes it difficult to make future changes, for instance, perhaps change from using one email service to another. Also, this sort of approach makes testing difficult as testing the Example class would inevitably cause calls tot eh EmailSender class and you may want to avoid sending out emails to real live customers or even use an email server at all as you’re really interested in testing the behavior of the Example class. It would be nice if one could swap out the real EmailSender class for a fake stand-in just for the purposes of testing the Example class.

That can certainly be made possible through Dependency Injection. Considered the modified example below:

public class Example
{
    public Example(IEmailSender emailSender)
    {
        emailSender.SendEmail("tom@test.net");
    }

}

public interface IEmailSender
{ 
    bool SendEmail(string email); 
}

public class EmailSender : IEmailSender
{
    public bool SendEmail(string email)
    {
        // send email here.
        return true;
    }
}

In the example above, the Example class uses an email sender object that is injected into its constructor. As far as it is concerned, it doesn’t know how that object gets made, it simply specifies that it needs one and one is provided for it to use. This brings about some flexibility:

  • You now have the freedom to go from one concrete implementation of the EmailSender class to another without making any changes to the Example class.
  • For the same reason, testing becomes simpler as you can inject a Mock EmailSender class to stand-in for the real thing, while you exercise the various features of the example class.

Setup DI in ASP.NET

Well, how do we do this in ASP.NET? The basic dotnet new web template already comes wired up with Dependency Injection that you can just create classes and begin using it. Usually, you do this in Program.cs or in Startup.cs, in older versions of the template. Here, I’m using the AddScoped method of the DI Container to denote that whenever an IEmailSender is requested, create an instance of the EmailSender class and offer this same instance if requested again within the same HTTP request. You can learn more about scopes and lifetimes in the next section titled DI Container Scopes.

var builder = WebApplication.CreateBuilder(args);

// Register the IEmailSender interface with DI Container
// DI Container - when anyone asks for an IEmailSender, give them an instance of EmailSender
builder.Services.AddScoped<IEmailSender, EmailSender>();
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Once the class/interface has been registered with the DI Container, you can request it in the constructor of any other class and ASP.NET will create the appropriate instantiation and provide it.

In the example below, the ExampleController class has a dependency on the IEmailSender interface that we registered earlier. This registration allows for one to specify IEmailSender as a parameter of its constructor and ASP.NET will provide the correct implementation of it, at runtime.

[Route("api/[controller]")]
public class ExampleController : Controller
{
    private readonly IEmailSender emailSender;

    public ExampleController(IEmailSender emailSender)
    {
        this.emailSender = emailSender;
    }

    // GET: api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        emailSender.SendEmail("tom@test.com");
        return new string[] { "value1", "value2" };
    }
}

DI Container Scopes

When you register an Interface and its concrete implementation in the .NET DI Container, you must specify one of three scopes or lifetimes for it:

  • Singleton: When the DI container instantiates an object of a class registered as singleton, it will hand the same instance off to anyone who requests for this class for the entire lifetime of the application. For instance, say you have some configuration information stored externally that you are loading into an object and this information is the same for all consumers of it. In this scenario, you may choose to opt for a singleton lifetime.
  • Scoped: This lifetime instructs the DI container to hand the same instance of an object throughout the entirety of a single HTTP request. If that Interface is requested multiple times within the context of a single request, the container hands that same instance off, instead of creating a new one.
  • Transient: The DI container will create a new instance every time one is requested.

Closing Remarks

In this article, we’ve covered the essentials of getting started with Dependency Injection in .NET. Review the documentation for more advanced concepts such as – how to provide multiple implementations for a single interface, how to add items to the container in such a way that one concrete implementation doesn’t accidently override a previously registered implementation and other more advanced scenarios.

Leave a Comment

Your email address will not be published. Required fields are marked *