Short Summary
We basically know 3 types of lifetimes when it come to ASP.NET Core
- Singleton - One instance is created and is shared across all users
- Transient - Whenever someone is asking for a service always a new instance is created
- Scoped - One instance per request
Setup
Let's create three services, for each lifetime one:
public class MySingletonService
{
public Guid ServiceId { get; set; } = Guid.NewGuid();
}
public class MyScopedService
{
public Guid ServiceId { get; set; } = Guid.NewGuid();
}
public class MyTransientService
{
public Guid ServiceId { get; set; } = Guid.NewGuid();
}
Now we need two pages. We reuse the template which comes shipped with Visual Studio. Therefore we adapt the Index.razor and Counter.razor in this way:
@page "/"
@inject MySingletonService singletonService
@inject MyScopedService scopedService
@inject MyTransientService transientService
<h1>Index</h1>
<ul>
<li>Singleton Service - @singletonService.ServiceId</li>
<li>Scoped Service - @scopedService.ServiceId</li>
<li>Transient Service - @transientService.ServiceId</li>
</ul>
@page "/"
@inject MySingletonService singletonService
@inject MyScopedService scopedService
@inject MyTransientService transientService
<h1>Counter</h1>
<ul>
<li>Singleton Service - @singletonService.ServiceId</li>
<li>Scoped Service - @scopedService.ServiceId</li>
<li>Transient Service - @transientService.ServiceId</li>
</ul>
They look the same only the header is different. Now we want to check a few scenarios:
- Start the app and land on our home page
- Navigate to the counter page from the navigation menu
- Reload the index page with F5
- Open the page in incognito mode
Blazor Client
Now we have a look how the ID's look on the client side
Home page | Navigate to counter | Hard reload | Incognito mode | |
---|---|---|---|---|
Singleton | 17bbf702-b870-4242-a016-53440d85c30c | 17bbf702-b870-4242-a016-53440d85c30c | 2cde0cc6-792d-4f24-8752-6662996e506b | 9ed59027-9a43-45ec-827e-2bc6e8fcde5c |
Scoped | 5526f6ea-0bfe-44fe-8264-a902286ec165 | 5526f6ea-0bfe-44fe-8264-a902286ec165 | ada9205d-af01-4c1b-86b5-de840f02cce5 | 7b6283d1-fdef-4a49-850d-c1a78111a849 |
Transient | 96707761-be74-4cf7-94d6-3fc940f9d016 | 732ed661-e833-4537-a7bf-9691f81d58bd | f218f73b-93b7-43d6-9cf2-f9a08960c489 | 036b450b-c486-49e5-9a9c-07b22f4e5713 |
Now let's have a look. First the obvious one: Transient. I guess it is not weird that all of the services are unique. Now let's come to Singleton. Why is it different for Hard reload and incognito? The answer is easy: The code runs on your local machine and not on a server, therefore this is basically like a new program running which initializes the container once more. Now Scoped: Why do we have the same ID when we navigate to a different page? Well that is the idea behind a single page application. You don't make a new "request". You are still in the same request as you started. That also explains why a hard reload gives you a new ID, because now we have a new request.
Let's go to the server side
Blazor Server
Home page | Navigate to counter | Hard reload | Incognito mode | |
---|---|---|---|---|
Singleton | 76844a38-eca3-4b75-b47a-1f3bbc5510c9 | 76844a38-eca3-4b75-b47a-1f3bbc5510c9 | 76844a38-eca3-4b75-b47a-1f3bbc5510c9 | 76844a38-eca3-4b75-b47a-1f3bbc5510c9 |
Scoped | a434c2f8-6e15-4787-bfc8-530cc1aebc9e | a434c2f8-6e15-4787-bfc8-530cc1aebc9e | 16f56278-38bc-4c88-a804-38d8682c655a | e3d32065-f79f-4812-adc1-5b8ad9176e0c |
Transient | ad787879-a91e-4721-820b-95481cd90a85 | 55a993dd-22de-42e5-ad92-4e69666462e6 | b4a5e6b2-5eb6-494d-b8df-9a9c763ca5c3 | 8fc3bc62-c4f8-4794-8ef6-6ff0b963950b |
The not surprising part first: We have the same picture for Transient in client as well as server. If we have a look at the Singleton we see a difference. The incognito mode does not lead to a new instance. And that makes sense, because actually the server is the same. Blazor server "just" opens a SignalR connection with the HTML content rendered by the server. The main book of work is done on the server. Last but not least Scoped. We see the exact same picture as with the client side blazor. Of course client and server share a lot of similarities and this is also true for the Scoped lifetime.
Conclusion
Blazor client and server have a slightly different lifetime scope than your "normal" ASP.NET Core Web API or MVC project.