Dependency Injection Lifetimes In ASP.NET CORE

Introduction

 
When ASP.NET Core receives any request, it uses dependency injection container to resolve the required services & process request. When a webhost is built, framework services are registered with dependency injection container, for example: logging, routing, environment dependencies, configuration etc. When ASP.NET Core receives any request which needs framework component like controllers, the framework component is activated along with its dependencies.
 
Kestrol, the ASP.NET Core web server passes each request to suitable middleware; for example; HTTP requests are passed to MVC Middleware which chooses suitable controller to process request.
 
Dependency Injection Lifetimes In ASP.NET CORE
 
The controllers are not registered directly in dependency injection container but during the activation process of framework component, controller instance is resolvable in dependency injection. In simple words, services are registered with container at start-up and resolved from container at runtime - when they are required.
 
When registering any service with container, it is important to consider carefully the lifetime of service because chosen lifetime affects the reuse of service instances. Dependency injection container manages all instances of services it creates. Once the lifetime is passed, this service is disposed or released for garbage collection. Extension methods are used to define lifetime using IServiceCollection when registering the service. There are three type of service lifetimes: Transient , Singleton & Scoped
 

Example Scenario

 
With the help of an example, we will identify which service is suitable in which conditions. In our example, we have one Lucky Draw application that selects a lucky winner through a drawing. Suppose, a lucky winner is shown two screens in a company's own offices and also sent to agents who sell tickets as resellers. We need consistent data, the same one winner should be shown to all customers in a home office screen as well as the agent's (resellers) screens.
 
I have used a simple API application with many in-memory contestants. The constructor chooses one random contestant as winner which is returned as string when GetWinner method is called.
  1. public class LuckyDrawService  
  2.     {  
  3.         private readonly string[] Contestants = { "A100","B200","C300","D400","E500","F600","G700","H800""I900""J1000""K1100""L1200""M1300""N1400""O1500""P1600""Q1700",   
  4.             "R1800""S1900""T2000""U2100" };  
  5.         private readonly string lucky;  
  6.         public LuckyDrawService()  
  7.         {              
  8.             lucky = Contestants[new Random().Next(0,20)];  
  9.         }  
  10.         public string GetWinner() => lucky;  
  11.     }  
We have custom middleware which utilizes LuckyDrawService service when handles each request, it gets a winner from service and adds to the message variable.
  1. public class AgentMiddleware  
  2.     {  
  3.         private readonly RequestDelegate _broadcast;  
  4.         public AgentMiddleware(RequestDelegate broadcast)  
  5.         {  
  6.             _broadcast = broadcast;  
  7.         }  
  8.   
  9.         public async Task InvokeAsync(HttpContext request, LuckyDrawService luckyDrawService)  
  10.         {  
  11.             string message = $"Broadcast to Agents Screen: Winner is {luckyDrawService.GetWinner()}";  
  12.               
  13.             request.Items.Add("AgentWinner", message);  
  14.   
  15.             // Call the next delegate/middleware in the pipeline  
  16.             await _broadcast(request);  
  17.         }  
Also, I have injected LuckyDrawService in HomeController contrtoller as well.
  1. [ApiController]  
  2.     public class HomeController : ControllerBase  
  3.     {  
  4.         private readonly LuckyDrawService _luckyDrawService;  
  5.         public HomeController(LuckyDrawService luckyDrawService )  
  6.         {  
  7.             _luckyDrawService = luckyDrawService;  
  8.         }  
  9.   
  10.         [Route("")]  
  11.         public IActionResult Index()  
  12.         {  
  13.             string message = $"Broadcast to Company Home Screen: Winner is {_luckyDrawService.GetWinner()}";  
  14.   
  15.             var screen = new List<string>  
  16.             {  
  17.                 HttpContext.Items["AgentWinner"].ToString(),  
  18.                 message  
  19.             };  
  20.   
  21.             return Ok(screen);  
  22.         }  

Transient

 
Every time a new instance for service is created and returned by container when called.
 
To use transient lifetime, add AddTransient in ConfigureService method in Startup.cs
  1. services.AddTransient<LuckyDrawService>();  
Every dependant class using transient will receive a unique instance. AgentMiddleware & HomeController has its own instance. Note here that values received in middleware & controller are different,
 
Dependency Injection Lifetimes In ASP.NET CORE
After Refresh (F5), you can see that a winner is changed in both middleware and controller,
 
Dependency Injection Lifetimes In ASP.NET CORE
 

Singleton

 
The application has one shared instance and lifetime for instance is the lifetime of application. The one instance is created and injected into all dependant classes.
 
To use singleton lifetime, add AddSingleton in ConfigureService method in Startup.cs
  1. services.AddSingleton<LuckyDrawService>();  
After running the application, we can see that middleware and controller have received the same winner,
 
Dependency Injection Lifetimes In ASP.NET CORE
 
After Refresh (F5), the winner is still the same,
 
Dependency Injection Lifetimes In ASP.NET CORE
 

Scoped

 
The lifetime of the instance of scoped service is the lifetime of scope from which it is resolved. If a service instance is created, it is shared between middleware and controller. It is intermediate between transient & singleton.
 
To use scoped lifetime, add AddScoped in ConfigureService method in Startup.cs
 
We can notice that the winner is the same in both middleware and controller

Dependency Injection Lifetimes In ASP.NET CORE
 
After Refresh(F5), we can notice that winner is changed but they are the same in both middleware and controller.
 
Dependency Injection Lifetimes In ASP.NET CORE
 

Conclusion

 
We can notice how instances are resolving and can make different results. Transient was generating a different winner in middleware and controller; it was also changing with refresh of page - it is because new instance was created with each call.
 
Singleton was giving the same winner in middleware and controller; even with refresh of the page, the result was the same. It is because one instance was created with the lifetime application.
 
It can help in performance if service is used frequently by fewer objects allocations & less load on GC. It is thread-safe. While choosing it, the frequency for usage of service & memory consumption should be considered as well. If usage of service is very rare and it occupies a lot of memory, it can cause performance issues or memory leaks as it never is released for garbage collection.
 
Scoped has a lifetime of scope of request so instance is created with request and shared in middleware and controller.
 
Also,  service should not depend on a service whose lifetime is shorter than its own.
 
Transient Scoped Singleton
Transient
Yes
Yes Yes
Scoped No Yes Yes
Singleton No No
 No