| logging and monitoring in a .NET :👈 | 👉:Engineering-Practices-NET-Core |
In .NET Core, dependency injection (DI) service lifetimes |
DI Service Lifetimes (Transient, Scoped, Singleton)
In .NET Core, dependency injection (DI) service lifetimes—transient, scoped, and singleton—determine how instances of a service are created, shared, and managed. They are configured during application startup, typically in Program.cs, using methods like AddTransient(), AddScoped(), and AddSingleton().
Transient
Creation: A new instance is created every single time the service is requested from the DI container.
Sharing: No instances are shared. Even within the same HTTP request, if a transient service is injected into multiple components, each will receive its own unique instance.
Best for: Lightweight, stateless services that should not share state. Examples include helper classes, random number generators, and simple utility services.
Registration:
services.AddTransient<IMyTransientService, MyTransientService>();
Scoped
Creation: A new instance is created once per client request (or "scope"). In a typical ASP.NET Core web application, this means one instance is created per HTTP request.
Sharing: The same instance is shared across all components that request it within the same request scope. A new request, however, will create a new instance.
Best for: Services that need to maintain state throughout a single, contained operation, such as a database context (DbContext) in Entity Framework Core. This ensures that all components within a single request use the same database transaction.
Registration:
services.AddScoped<IMyScopedService, MyScopedService>();
Singleton
Creation: Only one instance is created for the entire application's lifetime. This happens the first time the service is requested.
Sharing: The exact same instance is shared across all requests and all components in the application until the application is shut down.
Best for: Stateless and thread-safe services that are expensive to create, such as a logging service, a configuration manager, or an in-memory cache. Because the instance is shared, it must be designed to be thread-safe.
Registration:
services.AddSingleton<IMySingletonService, MySingletonService>();
Important considerations and pitfalls
"Captive dependency" issue
You should never inject a service with a shorter lifetime (scoped or transient) into a service with a longer lifetime (singleton). Doing so can lead to a "captive dependency" bug, where the longer-lived service holds onto an instance of the shorter-lived service for its entire lifetime, overriding its intended behavior. For example, injecting a scoped DbContext into a singleton background service would cause the DbContext instance to live forever, leading to memory leaks and incorrect state.
Solving captive dependencies
If you must use a shorter-lived service inside a longer-lived one, you can manually create a service scope. This is often done by injecting IServiceScopeFactory into the singleton service and then creating a new scope to resolve the shorter-lived dependency only when you need it.
Registration in Program.cs
In .NET 6 and later, service registrations happen directly in Program.cs.
var builder = WebApplication.CreateBuilder(args);
// Register services
builder.Services.AddTransient\();
builder.Services.AddScoped\();
builder.Services.AddSingleton\();
Quick Analogy
Imagine you’re at a coffee shop:
Transient
Every time you order a coffee, you get a brand new cup.
Scoped
You get one cup per visit; you can refill it during your stay, but next time you visit, it’s a new cup.
Singleton
The shop gives you one thermos for life; you carry it everywhere, and it’s refilled forever.
💡 Pro Tip for Large-Scale Apps (like your modular platforms)
- Transient: Great for small, stateless utility classes.
- Scoped: Ideal for request-bound data (e.g., EF Core
DbContext). - Singleton: Perfect for configuration, caching, or services that must be reused across the app.
In multi-tenant SaaS, be careful with singletons holding tenant-specific data — they can leak state between tenants.
| logging and monitoring in a .NET :👈 | 👉:Engineering-Practices-NET-Core |