Implementing dependency injection in ASP.NET Core
In this post, we will be showing how to implement Dependency Injection in an ASP.NET Core application. We'll start by looking at the problems that arise from not using the inversion of control pattern in an application containing dependencies. After that, we'll see how to modify and refactor our code by using dependency injection and using the dependency injection container, which we will briefly introduce.
An application containing dependencies
Throughout this post, we will be working on a simple ASP.NET Core web application, using ASP.NET Core 6. The aim of the application is to display the current date on the index page. Let's create it in 3 steps.
In this first instance, we'll create the application without using any form of dependency injection. This should not be the preferred method to inject dependencies in an application, as we'll see later.
Step 1: Create an ASP.NET Core web application
First, create a new ASP.NET Core web application. You can do this from the command line by typing the following:
dotnet webapp -o DIContainer_Demo
Step 2: Create a class containing the service we want to use
The aim is to display the current date on the index page of the application. To do this, first create a new class, called ShortDate
. This will contain the logic to get the date that we'll later display in the index page.
ShortDate
will contain the following code:
public class ShortDate
{
private readonly string _date;
public ShortDate()
{
_date = DateTime.Now.ToShortDateString();
}
public string GetDate()
{
return _date;
}
}
Step 3: Use the ShortDate service in the IndexModel class.
The IndexModel
has an OnGet
method, which populates the model used to build and return the index page content. This is where we'll call GetDate
from the ShortDate
class. The IndexModel
class has therefore a dependency on ShortDate
.
In order to be able to use ShortDate
, we need to create an instance of the class. For now, we are doing this inside of the OnGet
method. Keep reading to find out the preferred way to do this!
public class IndexModel : PageModel
{
public string Date { get; set; }
public void OnGet()
{
var _shortDate = new ShortDate();
var date = _shortDate.GetDate();
Date = date;
}
}
The index page displays the date retrieved using ShortDate
from the OnGet
method:
@page
@model IndexModel
@{
}
<div class="text-center">
<h4 class="display-5">Date: @Model.Date</h4>
</div>
Our application works the way we want to, yet if you're familiar with the concept of Inversion of Control, you will have noticed there is something fundamentally wrong with how our service is instantiated and consumed by the OnGet
method in the IndexModel
class.
Dependency Injection and Inversion of Control
To populate the page model, the OnGet
method calls the GetDate
method on ShortDate
. This is called a dependency, because the OnGet method in the IndexModel
class is dependent on the ShortDate
service to be able to run.
Whenever there is a dependency, an instance of the service on which the class depends needs to be created. Currently, the OnGet
method is responsible for the creation of an instance of ShortDate
, on which it depends. This is an example of tight coupling, as the OnGet
method is tightly coupled to the current implementation of ShortDate
– a change in the implementation of ShortDate
requires a change in the OnGet
method. This makes code hard to maintain, as new requirements would need manual updates to potentially several classes.
The aim of dependency injection is to make code more easily maintainable. By using inversion of control, the method that needs an instance of a service is no longer responsible for creating said instance. Instead, the constructor of the class will list in its parameters all the dependencies needed within that class, and inject instances of them when they are needed.
In order to fully implement inversion of control, we need to implement dependency injection by registering the services in the dependency injection container. The framework can then take responsibility for creating and injecting the required dependencies.
The Microsoft dependency injection container is built into ASP.NET Core. It allows services to be registered with the IServiceCollection
. When a service registered is requested, the container creates and injects an instance of that service.
Implementing Dependency Injection
Let's see how to implement dependency injection in our application in three steps.
Step 1: Introduce constructor injection
When using constructor injection, the dependencies of a method are passed into the class through the constructor, rather that the method creating those instances itself.
This is the IndexModel
class using constructor injection:
public class IndexModel : PageModel
{
private readonly ShortDate _date;
public IndexModel(ShortDate date)
{
_date = date;
}
public string Date { get; set; }
public void OnGet()
{
// We don't need to create an instance of ShortDate inside the method
// var _shortDate = new ShortDate();
var date = _date.GetDate();
Date = date;
}
}
The changes made to the IndexModel
class are:
- The
ShortDate
service has been added as a parameter of the constructor. - The injected dependency is stored into a private field so that it's accessible by the methods in the class.
- The instantiation of
ShortDate
inside theOnGet
method is removed. This is not needed as the instance of the service is already being injected by the constructor.
This implements inversion of control, as we are inverting the control of the creation of dependencies. The OnGet
method is no longer responsible for creating and passing dependencies. Instead, the constructor in the IndexModel
class takes up that responsibility.
Step 2: Create an abstraction of the service
Let's go one step further to fully implement inversion of control by using an abstraction of ShortDate
rather than its implementation. This will reduce the coupling between IndexModel
and its dependency, ShortDate
, and result in more loosely coupled code.
Extract the interface for ShortModel
and call it IDate
.
public interface IDate
{
string GetDate();
}
Make sure that ShortDate
is implementing IDate
if you are doing this manually.
public class ShortDate : IDate
{
private readonly string _date;
public ShortDate()
{
_date = DateTime.Now.ToShortDateString();
}
public string GetDate()
{
return _date;
}
}
Update the IndexModel
class to use the interface instead of the implementation.
public class IndexModel : PageModel
{
private readonly IDate _date;
public IndexModel(IDate date)
{
_date = date;
}
public string Date { get; set; }
public void OnGet()
{
// We don't need to create an instance of ShortDate inside the method
// var _shortDate = new ShortDate();
var date = _date.GetDate();
Date = date;
}
}
Step 3: Register your service in the Dependency Injection container
The last step we need to complete for our application to run without errors is to let the framework know how to resolve the dependency on IDate
at runtime.
To do this, register the service in the Dependency Injection container in Program.cs. Note that services are registered in the Program.cs file in ASP.NET Core 6. In earlier versions of ASP.NET Core, this is done within the ConfigureServices
method, located in the Startup class.
Registering the service takes only one line of code:
builder.Services.AddTransient<IDate, ShortDate>():
The service is registered in the IServiceCollection
, accessed from the builder.
Using the Add
extension method on the service collection registers our service with the container.
If we are not registering multiple implementations of the same interface type, where we place our registration is irrelevant as long as it happens before build
is called on the builder.
In this registration, we are using the overload of the AddTransient
that accepts two generic arguments: the abstract type (also called the service type) and the implementation type. The latter needs to be a class that implements the service type. In this case, we are registering the ShortDate
implementation of IDate
. To change the implementation used in the instantiation and injection of the service, the only change needed now would be to replace ShortDate
for the new implementation.
Note that AddTransient
is only one of the three possible lifetimes to register a service, but we'll leave the details on these for a different blog post.
Conclusion
In this post, we looked at how to implement dependency injection in an ASP.NET Core web application. We started with an example of an application where dependency injection hadn't been implemented. We explained how to refactor the code step by step to apply the inversion of control pattern, first using constructor injection, then using an abstraction of the service injected rather than its implementation, and lastly registering the service in the dependency injection container.