Life cycle of Dependency Injection
Understanding the lifecycle of services created using Dependency Injection is very important before using them. Creating services without understanding the difference between Transient, Singleton, and Scoped can cause the system to not work as expected. When a service requests another service through DI, it is important to know whether it received a new instance or an existing instance. Therefore, it is very important to be aware of the correct life cycle or life time while registering a service.
Service lifecycle management
Whenever we request a service, the DI Container decides whether to create a new instance or reuse the previously created instance. The service life cycle depends on when the instance is instantiated and how long it lives. We define the lifecycle when we register the service.
We learned how to use DI in the previous lesson. There are 3 lifecycle levels, this way we can decide how each service has a lifecycle.
Transient: A new instance is always created, every time it is requested.
Scoped: Create a new instance for all scopes (Each request is a scope). In scope, the service is reused
Singleton: Service is created only once.
Project example
Create a new Service Interface Create a new service named SomeService in the Services folder. Add 3 new interfaces. Each interface corresponds to a lifecycle in ASP.NET Core. The interface is simple, they contain each GetID method that returns a Guid.
Create a Service Now we will create a single service that implements all three interfaces:
The service generates a unique ID and it will be initialized and return the id in the GetID method. Now let's see the details
Transient
The Transient Service always creates a new one each time the service is requested
Sign up for Transient Service
Now inside the ConfigureServices method of Startup will register SomeService through the ITransientService interface.
Inject into Controller
Open the HomeController and inject 2 instances of SomeService as shown below:
First, we inject two service instances through the ITransientService interface in the HomeController constructor.
Next, we call the GetID method in each instance and assign it to the view using ViewBag.
View
Run the application and you will see 2 different Guids displayed on the screen. The proof is that we got 2 instances of the Transient service.
Scoped
Services with scoped lifecycle are created only once per request (scope). A new instance is created for each request and the instance is reused in the request.
Sign up for Scoped Service
In the ConfigureServices method register SomeService using the AddScoped method using the IScopedService interface.
Inject scoped service into Controller
Next inject this service into the Controller. We already have a transient service injected into the controller. Now nothing has changed but added a new one:
Add to action method 2 variables message3 and message4 for scoped service.
View
In view add 3 lines
Run the application. The instance is created only once per request, that's why we create 2 identical IDs. Now refresh the browser. The ID has changed because a new instance is created for each request.
Singleton
A single instance of the service is created when it is first requested. Then each subsequent request will only use that same instance. The new request does not create a new instance, it is reused.
Register Singleton Service
Singleton service is registered using the AddSingleton
Inject Singleton service into Controller
First we inject 6 instances of SomeService. Two instances for each interface.
View
Run the application and you will see that the IDs generated from the Singleton service are always the same and will not change even if you refresh the application. You can see in the picture below
So which one should be used?
Transient service is the safest way to create, since you are always creating a new instance. But because of that it will create each time you request so will use more memory and resources. This may adversely affect performance if too many instances are created.
Using Transient Service is suitable when you want to use for lightweight and small and stateless services.
Scoped service is better when you want to maintain state within a request.
Singleton is created only once, it is not destroyed until application shutdown. Any memory usage with these services builds up over time and it fills up. But also helps us save memory if handled well because they are created only once and used everywhere.
Use Singleton when you need to maintain system-wide state. Configuration, application parameters, service logging, data caching... are common examples of Singleton usage.
Inject service with different lifecycle into another service
Be careful, when injecting services into other services with different lifecycles. Let's see an example Singleton Service that depends on another service registered with a lifecycle of Transient.
When the request first arrives, an instance of the Singleton is created.
When the second request arrives, this instance of the Singleton is reused. And this singleton already contains an instance of the transient service. So it is not recreated. This invisibly converted a transient service into a singleton service.
Services with a lower lifecycle injected into a service with a higher lifecycle will change a lower lifecycle service to a higher lifecycle service. This will make debugging harder and it should be avoided. From low to high are Transient, Scoped and Singleton.
So remember the rule:
Never inject Scoped & Transient service into Singleton service
Never inject Transient Service into Scope Service
Last updated