How to configure Dependency Injection in ASP.NET Core application?

If you are using ASP.NET Core framework to build your web application, you will be happy to know that with ASP.NET Core, Dependency Injection (DI) is now a first class citizen in ASP.NET. The same DI container is used by the whole ASP.NET stack as well as your application code. In this post, I’ll tell you how to configure Dependency Injection in ASP.NET Core application.
Dependency Injection
If you are not comfortable with concepts like Dependency Injection (DI) or Inversion of Control (IoC) or Dependency Inversion Principle, please use the my linked posts to understand these concepts clearly.
In simple words, Dependency Injection (DI), is a type of IoC where the creation and binding of a dependency is moved outside of the class that depends on it.
Say suppose when you pack your lunch, you know what you have packed inside it. But when you know that the food will be provided, you know that you need not to worry about lunch, it will just be served when you need it.
Example
Below is the definition of Product, ProductService class that implements an interface named `IProductService`. This service is responsible to provide products to the consumer within the application.
The idea behind this service implementing this interface is that our application can use the interface for reference and the actual implementation will be available at run-time with the magic of DI container.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double Price { get; set; }
}
public interface IProductService
{
IEnumerable<Product> GetAll();
}
public class ProductService
{
public IEnumerable<Product> GetAll()
{
// get data from database or request it from another service using REST, etc.
return new List<Product>();
}
}
Register the service(s)
We need to add this ProductService to the DI container first so that application can consume it on demand with the help of just referring IProductService type. The file named Startup.cs has a method named ConfigureServices where this needs to be done.
We have access to IServiceCollection services in this method that has important methods mentioned below. The basic idea is to map the interface type to the concrete type definition. Anytime your application requests an instance of concrete type, an instance will be provided by using the DI container. This instance can be shared, new or scope within the application which depends on how the type is mapped with IServiceCollection services.
If AddTransient or `ServiceLifeTime.Transient`is used then a new instance of the mapped type is available to the consumer.
If AddSingleton or `ServiceLifeTime.Singleton`is used then a shared instance of the mapped type is available to the consumer.
If AddScoped or `ServiceLifeTime.Scoped`is used then a scoped instance of the mapped type is available to the consumer. Scoped in this case means scoped to an HTTP request i.e. this instance will be singleton while the current request is in scope.
You may also add an existing instance to the DI container using the method `AddInstance`.
Below is an illustration of how you can use these methods:
services.AddTransient<IProductService, ProductService>(); services.AddTransient(typeof (IProductService), typeof (ProductService)); services.Add(new ServiceDescriptor(typeof(IProductService), typeof(ProductService), ServiceLifetime.Transient)); services.Add(new ServiceDescriptor(typeof(IProductService), p => new ProductService(), ServiceLifetime.Transient)); services.AddSingleton<IProductService, ProductService>(); services.AddSingleton(typeof(IProductService), typeof(ProductService)); services.Add(new ServiceDescriptor(typeof(IProductService), typeof(ProductService), ServiceLifetime.Singleton)); services.Add(new ServiceDescriptor(typeof(IProductService), p => new ProductService(), ServiceLifetime.Singleton)); services.AddScoped<IProductService, ProductService>(); services.AddScoped(typeof(IProductService), typeof(ProductService)); services.Add(new ServiceDescriptor(typeof(IProductService), typeof(ProductService), ServiceLifetime.Scoped)); services.Add(new ServiceDescriptor(typeof(IProductService), p => new ProductService(), ServiceLifetime.Scoped)); services.AddInstance<IProductService>(new ProductService()); services.AddInstance(typeof(IProductService), new ProductService()); services.Add(new ServiceDescriptor(typeof(IProductService), new ProductService()));
Extension to the rescue
There can be many mapping statements like this for registration process, in this case you should create an extension method to the `IServiceCollection` to keep your method and file clean. An example of this is how we add MVC to the container, e.g. `services.AddMvc();`
A custom extension to add logically grouped services to the `IServiceCollection` services needed by the middleware will look similar to this:
public static class ServiceCollectionExtensions
{
public static IServiceCollection RegisterServices(
this IServiceCollection services)
{
services.AddTransient<IProductService, ProductService>();
// and a lot more Services
return services;
}
}
Now the method `ConfigureServices` looks much more cleaner and thin:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.RegisterServices();
}
Usage
A mapped service with the container can be requested in the application very easily.
For example, to request an instance of `ProductService` in controller, all we need to write is following:
public class HomeController : Controller
{
private readonly IProductService _productService;
public HomeController(IProductService productService)
{
_productService = productService;
}
// …
}
We can also request an instance inside a View like this:
@inject Services.IProductService productService;
The statement above is simple, the first part after the `@inject` directive defines the interface. The second part is the name of the variable which will hold the instance.
This is good for per view basis but to inject a service globally into all Views, we just need to move this statement to the `_ViewImports.cshtml` file. An example of a global injection defined for `ApplicationInsights` is:
@inject Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration TelemetryConfiguration
Finally, we can use the variable in our view just like any other variable as shown below:
@if (productService.All().Any())
{
<ul>
@foreach (var product in productService.All().OrderBy(x => x.Name))
{
<p>@product.Name (@product.Description)</p>
}
</ul>
}
@Html.DropDownList("Products", productService.All()
.OrderBy(x => x.Name)
.Select(x => new SelectListItem
{
Text = x.Name,
Value = x.Id
}))
Just for your information, DI also works in MiddleWares, TagHelpers and ViewComponents.
Conclusion
You can use DI container almost anywhere in the application (except in HTMLHelpers, because these are extension methods) for custom services as well as services registered by ASP.NET Core. This helps to keep application clean, light-weight (no need of any other DI container implementation), maintainable and testable. Hope this post easily explains how to configure Dependency Injection in ASP.NET Core application.
