- Home
- .NET tutorials
- How to implement dependency injection in ASP.NET Core
How to implement dependency injection in ASP.NET Core
Published: Friday 26 February 2021
ASP.NET Core has support for the dependency injection (DI) design pattern and we will show how to implement it.
Resources
To try out this application, download the code example from our code examples section.
The Service Lifetimes
In ASP.NET Core, dependency injection is supported by three different service lifetimes.
Singleton
An instance of a singleton lifetime service will last for the lifetime of the application.
Scoped
A scoped lifetime service can be defined implicitly or explicitly.
Transient
An object using the transient lifetime service gets a new instance created every time it's injected.
How the different dependency injection service lifetimes work in ASP.NET Core MVC app
Using Dependency Injection in ASP.NET Core
Now that we got an overview of what dependency injection is, we are now going to put it into practice in ASP.NET Core.
// ISingletonService.cs
public interface ISingletonService
{
string Time { get; set; }
}
// SingletonService.cs
public class SingletonService : ISingletonService
{
public string Time { get; set; }
public SingletonService()
{
Time = DateTime.UtcNow.ToString("HH:mm:ss.ffffff");
}
}
// IScopedService.cs
public interface IScopedService
{
string Time { get; set; }
}
// ScopedService.cs
public class ScopedService : IScopedService
{
public string Time { get; set; }
public ScopedService()
{
Time = DateTime.UtcNow.ToString("HH:mm:ss.ffffff");
}
}
// ITransientService.cs
public interface ITransientService
{
string Time { get; set; }
}
// TransientService.cs
public class TransientService : ITransientService
{
public string Time { get; set; }
public TransientService()
{
Time = DateTime.UtcNow.ToString("HH:mm:ss.ffffff");
}
}
Next, we need to add these services to the IServiceCollection instance in the Startup class. This will register the service to be used in DI.
// Startup.cs
public class Startup
{
...
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<ISingletonService, SingletonService>();
services.AddScoped<IScopedService, ScopedService>();
services.AddTransient<ITransientService, TransientService>();
}
...
}
Now, to test this, we are going to create a DI controller.
// DIController.cs
[Route("di")]
public class DIController : Controller
{
protected readonly ISingletonService _singletonService;
protected readonly IScopedService _scopedService;
protected readonly ITransientService _transientService;
public DIController(ISingletonService singletonService, IScopedService scopedService, ITransientService transientService)
{
_singletonService = singletonService;
_scopedService = scopedService;
_transientService = transientService;
}
public IActionResult Index()
{
return null;
}
}
What Happens When We Initialise Each Service?
When we initialise each service, we set a Time property. This is set to the current time in hours, minutes, seconds and microseconds.
Time property for each of the services.
// DIModel.cs
public class DIModel
{
public string SingletonTime { get; set; }
public string ScopedTime { get; set; }
public string TransientTime { get; set; }
}
// DIController.cs
[Route("di")]
public class DIController : Controller
{
protected readonly ISingletonService _singletonService;
protected readonly IScopedService _scopedService;
protected readonly ITransientService _transientService;
public DIController(ISingletonService singletonService, IScopedService scopedService, ITransientService transientService)
{
_singletonService = singletonService;
_scopedService = scopedService;
_transientService = transientService;
}
public IActionResult Index()
{
var model = new DIModel();
model.SingletonTime = _singletonService.Time;
model.ScopedTime = _scopedService.Time;
model.TransientTime = _transientService.Time;
return View(model);
}
}
@{
// Index.cshtml
}
@model DIModel
<h2>Singleton</h2>
<p>Controller Singleton: @Model.SingletonTime</p>
<h2>Scoped</h2>
<p>Controller Scoped: @Model.ScopedTime</p>
<h2>Transient</h2>
<p>Controller Transient: @Model.TransientTime</p>
Initialise Each Service - The Results
So we've loaded up the page, and refreshed it so we get two sets of results.
How the services get initialised in dependency injection when we load the page
How the services get initialised in dependency injection when we refresh the page
The singleton service has the same value in the Time property in each instance. This is expected as we have kept the application running for both times we loaded the page.
Time property being displayed.
Inject the Services in a View
Next, we are going to modify the view by injecting the services we created.
Time property for when it's injected in the controller and when it's injected in the view.
@{
// Index.cshtml
}
@using RoundTheCode.Di.Services
@model DIModel
@inject ISingletonService singletonService
@inject IScopedService scopedService
@inject ITransientService transientService
<h2>Singleton</h2>
<p>Controller Singleton: @Model.SingletonTime</p>
<p>View Singleton: @singletonService.Time</p>
<h2>Scoped</h2>
<p>Controller Scoped: @Model.ScopedTime</p>
<p>View Scoped: @scopedService.Time</p>
<h2>Transient</h2>
<p>Controller Transient: @Model.TransientTime</p>
<p>View Transient: @transientService.Time</p>
Inject the Services in a View - The Results
So we've loaded up the page and got two sets of results.
Using dependency injection in a controller and in a view
As we would expect, the singleton service has the same value regardless of whether it's injected in the controller and view.
Time property is different in the controller and view.
Another Way of Implementing Dependency Injection
There is another way of injecting dependencies into a class.
IServiceProvider instance.
IServiceProvider instance to call each service by calling it's GetRequiredService method, and passing in the type, either as a parameter, or as a generic method.
// DIController.cs
[Route("di")]
public class DIController : Controller
{
...
public DIController(IServiceProvider serviceProvider)
{
_singletonService = serviceProvider.GetRequiredService<ISingletonService>();
_scopedService = serviceProvider.GetRequiredService<IScopedService>();
_transientService = serviceProvider.GetRequiredService<ITransientService>();
}
...
}
@{
// Index.cshtml
}
@using RoundTheCode.Di.Services
@using Microsoft.Extensions.DependencyInjection;
@model DIModel
@inject IServiceProvider serviceProvider
@{
var singletonService = serviceProvider.GetRequiredService<ISingletonService>();
var scopedService = serviceProvider.GetRequiredService<IScopedService>();
var transientService = serviceProvider.GetRequiredService<ITransientService>();
}
...
The main advantage of doing it this way is to avoid a large number of dependencies being injected.
IServiceProvider instance, we can get the required services as and when we need them.
Injecting Services into Other Services
ASP.NET Core allows the ability to inject services into other services.
ISingletonService instance, we could inject the ITransitionService instance.
ISingletonService instance into the ITransitionService instance.
A circular dependency was detected for the service of type {type}.
The other thing to be aware of is what service lifetimes can be injected.
Cannot consume scoped service '{type}' from singleton '{type}'
ASP.NET Core wouldn't know what to do with a scoped service lifetime as it wouldn't know which scope it belongs to.
Define an Explicit Scoped Service
As we mentioned earlier, when a request is made in ASP.NET Core, it implicitly creates a scope.
// DIHostedService.cs
public class DIHostedService : IHostedService
{
protected readonly IServiceProvider _serviceProvider;
public DIHostedService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public Task StartAsync(CancellationToken cancellationToken)
{
using (var scope = _serviceProvider.CreateScope())
{
var singletonService = scope.ServiceProvider.GetRequiredService<ISingletonService>();
var scopedService = scope.ServiceProvider.GetRequiredService<IScopedService>();
var transientService = scope.ServiceProvider.GetRequiredService<ITransientService>();
Debug.WriteLine(string.Format("Singleton time is {0}", singletonService.Time));
Debug.WriteLine(string.Format("Scoped time is {0}", scopedService.Time));
Debug.WriteLine(string.Format("Transient time is {0}", transientService.Time));
}
using (var scope = _serviceProvider.CreateScope())
{
var singletonService = scope.ServiceProvider.GetRequiredService<ISingletonService>();
var scopedService = scope.ServiceProvider.GetRequiredService<IScopedService>();
var transientService = scope.ServiceProvider.GetRequiredService<ITransientService>();
Debug.WriteLine(string.Format("Singleton time is {0}", singletonService.Time));
Debug.WriteLine(string.Format("Scoped time is {0}", scopedService.Time));
Debug.WriteLine(string.Format("Transient time is {0}", transientService.Time));
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
In-turn, we also need to add the hosted service to the IServiceCollection interface.
// Startup.cs
public class Startup
{
...
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHostedService<DIHostedService>();
}
...
}
So when we explicitly define a scope, any services that are defined under the scoped service lifetime will be initialised when the scope begins, and will be disposed when the scope ends.
A Common Error
When starting out with dependency injection, an error similar to the following may appear.
No service for type '{type}' has been registered.
The reason for this error is because the service has not been declared inside the IServiceCollection interface.
ConfigureServices method from the Startup class.
IServiceCollection instance in the Startup class will resolve the issue.
Use a Delegate to Create an Instance
For our final look at dependency injection, we are going to use a delegate when declaring a service to the IServiceCollection instance.
AddSingleton, AddScoped, or AddTransient methods inside the IServiceCollection instance. Within these methods, we can use the extension that has a delegate.
IServiceParameter interface.
IServiceParameter instance to define each service, and explicitly define the parameters that we need to.
// Startup.cs
public class Startup
{
...
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<ISingletonService, SingletonService>((serviceProvider) =>
{
return new SingletonService(serviceProvider.GetRequiredService<ITransientService>());
});
...
}
...
}
Other Dependency Injection Packages
This is a summary of dependency injection in ASP.NET Core.
Related tutorials
How do you resolve scoped services in a background service?
Learn how to resolve scoped and transient services in an ASP.NET Core background service by creating a new scope and how it can also be used for multithreading.