Dependency Injection in C#/ASP.NET Core explained

If you found this article interesting consider sharing it!

Dependency Injection is a method to implement IoC (Inversion of Control). It helps to make classes completely loose coupled by moving the creation of the needed dependencies outside of the object that is using them.

Usually you will have a Client, a Service and an Injector.

The Client uses a Service which the Injector will inject into the Client:

Dependency Injection mechanism

Why should you use dependency injection

Dependencies are objects that another object depends on and they can be problematic.

If you want to change to a different implementation of the dependency, you’ll need to modify each class that uses it. It makes it more difficult to unit test. You can’t use a stub or mock, if you instantiate the dependency in the constructor for example.

Additionally, if the dependency has dependencies of its own, they need to be configured in each class that will use the dependency. This can become tedious as the code to configure the dependency is in multiple parts of your codebase.

By using dependency injection you can address these problems. Usually you will use an Interface, Base class or abstract class. This already will make the coupling more loose.

The Registration and configuration of the dependencies can be configured in one central place, instead of multiple places, and the lifecycles, cleanup and creation are handled by the framework.

Dependency Injection in C#/ASP.NET Core

ASP.NET Core was designed with Dependency Injection in mind. It offers multiple IoC containers which handle the DI (Dependency Injection).

Third party IoC Containers are available via NuGet as well and offer more functionality than the default out-of-the-box one. For example:

  • Auto-Registering
  • Scanning
  • Interceptors
  • Decorators

In the jargon of ASP.NET the injected dependencies are usually called Services and typically offer functionality to other objects such as controllers. They are not related to general or web services. It can be a database repository object, a facade for business/domain logic and more.

The default IoC Container supports Constructor Injection, which is injecting the dependency to a constructor argument:

public interface ICustomerManagement{} // Interface for service

public class CustomerManagement: ICustomerManagement{} // class implementing the Interface

public class CustomerController: ControlerBase{
    private readonly ICustomerManagement _customerManager; // DependencyDependency Injection is a method to implement IoC (Inversion of Control). It helps to make classes completel

    public CustomerManagement(ICustomerManagement manager) // Injection happens here
    {
        _customerManager = manager;
    }
}
 

Lifecycles

Services can be created with multiple lifecycles.

Singleton

Creates and share a single instance of a service for the entire lifetime of the application. This creates a Singleton instance of the service without the need to implement the Singleton pattern in it. The IoC Container will manage the instance.

Transient

A new instance of the dependency is created by the IoC Container every time the code asks for it.

Scoped

A new instance of the dependency is created once per request and will be shared for that request. For example when using the EF Core the AddDbContext extension method registers the context with a scoped lifetime by default. You can think of it as a “Singleton that is scoped to a request”.

Using Dependency Injection in C#/ASP.NET Core

Dependency Injection can be configured in the ConfigureServices method in the Startup class. You can define the scope by adding the service to the IServiceColletion with the corresponding method:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ILog, MyConsoleLogger>();
    services.AddTransient<ILog, MyConsoleLogger>();
    services.AddScoped<ILog, MyConsoleLogger>();
}

And then you can Inject the services in the constructor of the classes that need it as we’ve seen above.

If you want to inject a dependency into a middleware you’ll need to inject the service into the middleware’s Invoke or InvokeAsync method, because using constructor injection will throw a runtime exception (Lifetimes/Lifecycles are not compatible).

Method Injection:

using Microsoft.AspNetCore.Mvc;

public class HomeController : Controller
{
    public HomeController()
    {
    }

    public IActionResult Index([FromServices] ILog log) // Injection happens here
    {
        log.info("Index method executing");

        return View();
    }
}

As mentioned above, you should avoid disposing the services manually. They should never be disposed by the developer. This will be handled by the IoC Container so don’t interfere with it.

How to Design Services For Dependency Injection

Follow the recommendations from the official docs which are to:

  • Avoid stateful and static classes, methods or members.
  • Register it as a singleton if you need state
  • Do not directly instantiate dependent classes within services
  • Write small, well-factored and easily testable services

Benefits of using dependency injection in C#/ASP.NET Core

The classes like controllers do not use a concrete type, only the interface for it and it is managed by the IoC Container which is configured in a single place.

This makes it easy to change the implementation that is used, without modifying the classes/objects that use the dependency.

The registration is generally order independent, but not when registering multiple implementation of the same type. Which should be avoided most of the time anyways.

It also allows you to mock the dependencies while unit testing them. A full guide on how to test with dependency injections and Moq can be found here. Basically you’ll need to create Mocks of the dependencies and the needed methods using the interface and then you can pass them to the class under test via the constructor.

Registration methods

The official docs list the following registration methods:

MethodAutomatic
object
disposal
Multiple
implementations
Pass args
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()
Example:
services.AddSingleton<IMyDep, MyDep>();
YesYesNo
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})
Examples:
services.AddSingleton<IMyDep>(sp => new MyDep());
services.AddSingleton<IMyDep>(sp => new MyDep(99));
YesYesYes
Add{LIFETIME}<{IMPLEMENTATION}>()
Example:
services.AddSingleton<MyDep>();
YesNoNo
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})
Examples:
services.AddSingleton<IMyDep>(new MyDep());
services.AddSingleton<IMyDep>(new MyDep(99));
NoYesYes
AddSingleton(new {IMPLEMENTATION})
Examples:
services.AddSingleton(new MyDep());
services.AddSingleton(new MyDep(99));
NoNoYes
from https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#service-registration-methods

If you found this article interesting consider sharing it!
Advertisements