Dependency Injection.
Dependency Injection (DI) is a core concept in .NET Core, enabling developers to manage object lifetimes and dependencies more effectively. In this article, we'll explore the three types of DI lifetimes—Transient, Scoped, and Singleton—using a practical example.
You can clone the source code from: https://github.com/rodfari/DI
The Project Overview
In this project, we registered three types of services (Transient, Scoped, and Singleton) and observed how their lifetimes behave in an ASP.NET Core application. The services are implemented by the Lifecycle class, which generates a unique Guid upon instantiation. Here's how the services are registered:
Service Registrations in Program.cs:
builder.Services.AddTransient<ITransient, Lifecycle>();
builder.Services.AddScoped<IScoped, Lifecycle>();
builder.Services.AddSingleton<ISingleton, Lifecycle>();
Transient: A new instance is created every time the service is requested.
Scoped: A single instance is created for each HTTP request.
Singleton: A single instance is created and shared across the application’s lifetime.
Exploring the Code
Controller: HomeController
The HomeController requests two instances of each service type (Transient, Scoped, Singleton) through its constructor. These instances are then displayed in the view to highlight the difference in their lifetimes.
public class HomeController : Controller
{
public ITransient Transient1 { get; }
public ITransient Transient2 { get; }
public IScoped Scoped1 { get; }
public IScoped Scoped2 { get; }
public ISingleton Singleton1 { get; }
public ISingleton Singleton2 { get; }
public HomeController(
ITransient transient1,
ITransient transient2,
IScoped scoped1,
IScoped scoped2,
ISingleton singleton1,
ISingleton singleton2
) {
Transient1 = transient1;
Transient2 = transient2;
Scoped1 = scoped1;
Scoped2 = scoped2;
Singleton1 = singleton1;
Singleton2 = singleton2;
}
public IActionResult Index()
{
return View(new LifecycleVM{
Transient1 = Transient1.Guid,
Transient2 = Transient2.Guid,
Scoped1 = Scoped1.Guid,
Scoped2 = Scoped2.Guid,
Singleton1 = Singleton1.Guid,
Singleton2 = Singleton2.Guid
});
}
}
Service Implementation: Lifecycle
The Lifecycle class implements all three service interfaces (ITransient, IScoped, ISingleton). It assigns a new Guid during construction to uniquely identify each instance.
public class Lifecycle : IScoped, ITransient, ISingleton
{
public Lifecycle()
{
this.Guid = Guid.NewGuid();
}
public Guid Guid { get; set; }
}
The Output
The Index.cshtml file displays the Guid values of the six instances injected into the controller. These values help visualize the differences in service lifetimes.
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Service type</th>
<th scope="col">Instance</th>
</tr>
</thead>
<tbody>
<tr><th scope="row">Transient 1</th><td>@Model.Transient1</td></tr>
<tr><th scope="row">Transient 2</th><td>@Model.Transient2</td></tr>
<tr><th scope="row">Scoped 1</th><td>@Model.Scoped1</td></tr>
<tr><th scope="row">Scoped 2</th><td>@Model.Scoped2</td></tr>
<tr><th scope="row">Singleton 1</th><td>@Model.Singleton1</td></tr>
<tr><th scope="row">Singleton 2</th><td>@Model.Singleton2</td></tr>
</tbody>
</table>
Expected Behavior
Transient Services:
Transient1 and Transient2 will have different Guid values because a new instance is created every time the service is requested.
2. Scoped Services:
Scoped1 and Scoped2 will have the same Guid within a single HTTP request but will change across different requests.
3. Singleton Services:
Singleton1 and Singleton2 will always have the same Guid, regardless of the request, as they share the same instance throughout the application’s lifetime.
Key Takeaways
Transient:
Best for lightweight, stateless services.
Creates a new instance every time the service is requested.
Use Case: Operations that don't depend on shared state.
Scoped:
Provides a single instance per HTTP request.
Ensures consistency within the scope of a request.
Use Case: Database contexts or services that require consistent behavior during a request.
Singleton:
Shares the same instance across the entire application.
Requires careful thread-safety considerations.
Use Case: Caching, configuration, or services with shared state.
Conclusion
This project demonstrates the fundamental differences between Transient, Scoped, and Singleton service lifetimes in .NET Core. By understanding these lifetimes, you can choose the appropriate type for your services and ensure your application behaves predictably while optimizing resource usage.