Understanding the lifetime of the services created using the Dependency injection in ASP.NET Core is very essential, before starting to use them. Creating services without understanding the difference between Scoped Vs Transient Vs Singleton lifetime can result in application behaving erratically. When a service requests another service via DI, knowing whether it receives the new instance or an existing instance is also very important. Hence correctly specifying the lifetime while registering the service is of utmost importance.
Table of Contents
Managing the Service Lifetime
Whenever we ask for the service, the DI Container has to decide whether to return a new instance of the service or provide an existing instance. The Lifetime of the Service depends on how we instantiate the dependency. We define the lifetime when we register the service.
We learned how to register services in the article Dependency injection in ASP.NET core. There are three ways, by which you can do that. And it in turn decides how the DI Framework manages the lifecycle of the services.
- Transient: creates a new instance of the service, every time you request it.
- Scoped: creates a new instance for every scope. (Each request is a Scope). Within the scope, it reuses the existing service.
- Singleton: Creates a new Service only once during the application lifetime, and uses it everywhere
Let us understand the difference between these by using an example
Example Project
Let us create a sample project, to demonstrate how these Service lifetimes work.
Create an ASP.NET Core Web Application (use the web application model view controller template) and name it as DependencyInjection
. You can follow these tutorials on how to create a new project.
Creating the Service Interface
Create the service SomeService
in the services folder.
Add the three interfaces. One for each lifetime provided by the ASP.NET Core. The interface is very simple. It contains a single method GetID, which must return a unique ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public interface ITransientService { Guid GetID(); } public interface IScopedService { Guid GetID(); } public interface ISingletonService { Guid GetID(); } |
Creating the Service
Next, let us create a single service, which implements all the three Interfaces.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class SomeService : ITransientService, IScopedService, ISingletonService { Guid id; public SomeService() { id = Guid.NewGuid(); } public Guid GetID() { return id; } } |
The service generates a unique id, whenever we instantiate it. The GetID
method returns that id.
Now, let us look at each service lifetime in detail.
Transient
The Transient services always create a new instance, every time we request for it
Register the Transient Service
Now, under ConfigureServices
method of the startup
class register the SomeServive
via ITransientService
interface as shown below
1 2 3 | services.AddTransient<ITransientService, SomeService>(); |
Inject it into Controller
Open the HomeController
and inject the two instance of the SomeService
as shown below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class HomeController : Controller { ITransientService _transientService1; ITransientService _transientService2; public HomeController(ITransientService transientService1, ITransientService transientService2) { _transientService1 = transientService1; _transientService2 = transientService2; } public IActionResult Index() { ViewBag.message1 ="First Instance " + _transientService1.GetID().ToString(); ViewBag.message2 ="Second Instance "+ _transientService2.GetID().ToString(); return View(); } } |
First, We inject the two instance of the service via the interface ITransientService
in the constructor of HomeController
.
1 2 3 | public HomeController(ITransientService transientService1,ITransientService transientService2) |
Next, we query the GetID
method in each instance and pass it to the view using ViewBag
.
1 2 3 4 | ViewBag.message1 ="First Instance " + _transientService1.GetID().ToString(); ViewBag.message2 ="Second Instance "+ _transientService2.GetID().ToString(); |
View
Open the view and add the following code
1 2 3 4 5 6 | <h3>Transient Service</h3> @ViewBag.message1 </br> @ViewBag.message2 |
Run the application and you should see two different Guid are displayed on the screen. It is evident that we have received two new instances of the Transient service.
Scoped
The Services with scoped lifetime are created only once per each request (scope). I.e. It creates a new instance per request and reuses that instance within that request.
Now, let us see it with an example
Register the Scoped Service
Under the ConfigureServices
method register the SomeService
using the AddScoped
method & using the IScopedService
interface as shown below.
1 2 3 | services.AddScoped<IScopedService, SomeService>(); |
Inject scoped service into Controller
Next, inject the service into the controller. We already have transient service injected into the controller. Let us not change that. The HomeController is now as below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public class HomeController : Controller { ITransientService _transientService1; ITransientService _transientService2; IScopedService _scopedService1; IScopedService _scopedService2; public HomeController(ITransientService transientService1, ITransientService transientService2, IScopedService scopedService1, IScopedService scopedService2) { _transientService1 = transientService1; _transientService2 = transientService2; _scopedService1 = scopedService1; _scopedService2 = scopedService2; } public IActionResult Index() { ViewBag.message1 ="First Instance " + _transientService1.GetID().ToString(); ViewBag.message2 ="Second Instance "+ _transientService2.GetID().ToString(); ViewBag.message3 = "First Instance " + _scopedService1.GetID().ToString(); ViewBag.message4 = "Second Instance " + _scopedService2.GetID().ToString(); return View(); } } |
In the action method, message3
& message4
comes from the scoped service.
View
In the view add these lines
1 2 3 4 5 6 | <h3>Scoped Service</h3> @ViewBag.message3 </br> @ViewBag.message4 |
Run the application
The instance created only once per request, i.e. why you have the same IDs generated.
Now hit the browser refresh button. The id changes i.e. because DI creates the new instance of the service and reuses it
Singleton
The Singleton scope creates a single instance of the service when the request for it comes for the first time. After that for every subsequent request, it will use the same instance. The new request does not create the new instance of the service but reuses the existing instance.
Register the Singleton Service
Singleton services are registered using the AddSingleton
method.
1 2 3 | services.AddSingleton<ISingletonService, SomeService>(); |
Inject Singleton service into Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | public class HomeController : Controller { ITransientService _transientService1; ITransientService _transientService2; IScopedService _scopedService1; IScopedService _scopedService2; ISingletonService _singletonService1; ISingletonService _singletonService2; public HomeController(ITransientService transientService1, ITransientService transientService2, IScopedService scopedService1, IScopedService scopedService2, ISingletonService singletonService1, ISingletonService singletonService2) { _transientService1 = transientService1; _transientService2 = transientService2; _scopedService1 = scopedService1; _scopedService2 = scopedService2; _singletonService1 = singletonService1; _singletonService2 = singletonService2; } public IActionResult Index() { ViewBag.message1 ="First Instance " + _transientService1.GetID().ToString(); ViewBag.message2 ="Second Instance "+ _transientService2.GetID().ToString(); ViewBag.message3 = "First Instance " + _scopedService1.GetID().ToString(); ViewBag.message4 = "Second Instance " + _scopedService2.GetID().ToString(); ViewBag.message5 = "First Instance " + _singletonService1.GetID().ToString(); ViewBag.message6 = "Second Instance " + _singletonService2.GetID().ToString(); return View(); } } |
First, we are injecting 6 instances the SomeService. Two instances per each interface. This is done in the constructor of the Controller.
View
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | @{ ViewData["Title"] = "Index"; } <h2>Index</h2> <h3>Transient Service</h3> @ViewBag.message1 </br> @ViewBag.message2 <h3>Scoped Service</h3> @ViewBag.message3 </br> @ViewBag.message4 <h3>Singleton Service</h3> @ViewBag.message5 </br> @ViewBag.message6 |
Run the application. The ids generated from Singleton services are same and will not change even if you refresh the application. You can see it from the image below
Which one to use
Transient services are safest to create, as you always get the new instance. But, since the dependency injection system creates them every time they will use more memory & Resources and can have a negative impact on performance if you too many of them.
Use Transient lifetime for the lightweight service with little or no state.
Scoped services service is the better option when you want to maintain state within a request.
Singletons are created only once and not destroyed until the end of the Application. Any memory leaks in these services will build up over time. Hence watch out for the memory leaks. Singletons are also memory efficient as they are created once reused everywhere.
Use Singletons where you need to maintain application wide state. Application configuration or parameters, Logging Service, caching of data is some of the examples where you can use singletons.
Injecting service with different lifetimes into another
Be careful, while injecting service into another service with a different lifetime
Consider the example of Singleton Service, which depends on another Service which is registered with say the transient lifetime.
When the request comes for the first time a new instance of the singleton is created. It also creates a new instance of the transient object and injects into the Singleton service.
When the second request arrives the instance of the singleton is reused. The singleton already contains the instance of the transient service Hence it is not created again. This effectively converts the transient service into the singleton.
The services with the lower lifetime injected into service with a higher lifetime would change the lower lifetime service to a higher lifetime. This will make debugging the application very difficult and should be avoided at all costs.
Hence, remember the following rules
- Never inject Scoped & Transient services into Singleton service.
- Never inject Transient services into scoped service
Summary
In this tutorial, we looked at the lifetime scopes of the ASP.NET Core DI Framework. Knowing the difference between Scoped Vs Transient Vs Singleton is very important as it will impact the performance of the app.
Great explanation, thanks for the sharing such easy understanding examples.
Great explanation, thanks for the sharing such easy understanding examples.
Excellent! There are articles and then there are other articles…!
Awesome explanation , Great Job!!
wow! I finally got it!
Nice Article
Very well explained. Thanks!
Thanks it is very clear and helpful.
Finally getting it now. Thanks for this lesson.
Thank you.. understood well Thank you very much….
Fantastic… Very well precisely explained…
Fantastic…Very well precisely explained…
Great explanation, thanks!
Great explanation. Really helps. Thanks much
Really helpful, Thanks for sharing this.
Thanks for simple and accurate explanation. This really helped me understand DI lifetime better!
I have tested code but in scopedService defferent id generate. please resovle my issue.
Please recheck your code
You can refer to the source code
https://github.com/tekTutorialsHub/ASP-NET-Core-DependencyInjection
Can we do the reverse service injection like singleton into scoped and scoped into transient.
Yes you can.
Microsoft documentation mentions it: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#singleton
You can inject Singleton into Scoped & Transient
You can inject scoped into Transient
Great explanation, really helpful .😁
Awesome tutorial. Well organized.Thanks
Great efforts………….
Really Superb
Thanks
Really Helpful and thanks for the very good explanation.
Thanks, suraj
Excellent…Simply superb. Thanks a lot.
Its really very helpful article for the DI
This article helped me a lot! Your explanation was simple and easy to understand, Thank you.
Thanks
Great explanation, thank you very much.