In .NET dependency injection, choose a service lifetime by asking who should share the instance, how long it should live, and who should dispose it. A singleton that holds request-specific state can create the familiar shared-state bug: data or resources persist beyond the boundary where you expected them to end. That is an analogy, not a claim that every lifetime mistake has the same cause.
What a dependency injection lifetime controls
A lifetime is the container’s rule for reusing an instance and owning its disposal. In Microsoft’s built-in .NET dependency injection, the three choices are transient, scoped, and singleton. Their exact behavior can differ in other containers, so check the framework’s own rules before applying these details elsewhere. Microsoft Learn: service lifetimes
As an Amazon Associate I earn from qualifying purchases.
How the three lifetimes differ
| Lifetime | Reuse boundary | State and thread-safety | Disposal boundary | Typical fit |
|---|---|---|---|---|
| Transient | A new instance is created each time the service is requested from the container. | Separate resolutions do not share the instance’s state. If a singleton receives and retains a transient, however, that instance is retained by the singleton; consider how it is used and whether concurrent access requires thread safety. | The container disposes container-created instances according to its ownership and scope rules; do not dispose a container-owned dependency yourself. | Lightweight services that do not need shared state or a scope-wide instance. |
| Scoped | One instance is reused within a scope. In request-processing ASP.NET Core apps, the scope is commonly one client request. | State can be shared by code resolving the service inside that scope, but should not be treated as application-wide state. | Disposed when its scope ends. In a web request, that is ordinarily the end of the request. | Work tied to a request or another bounded operation. AddDbContext registers DbContext as scoped by default. |
| Singleton | The same instance is used for subsequent requests from the service provider, for that provider’s lifetime. | State is shared among callers. Singleton services must be thread-safe. | Lives until the service provider is disposed; it can retain its object graph and resources until then. | State-free shared services or deliberately shared, thread-safe state whose lifetime is application-wide. |
These are reuse rules, not a speed ranking. Repeated construction may cost something, but selecting a lifetime primarily means selecting a state-sharing and disposal boundary. Microsoft’s .NET dependency injection overview describes the built-in container’s role in managing services.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsWhy a lifetime mismatch resembles a familiar bug
Imagine a service designed to hold information for one request. If a singleton captures that service, the singleton may keep using the same request-specific object as later requests arrive. The result can resemble caching user data in a global object: one caller’s state may affect another, resources may be stale, and objects may remain in memory longer than intended. The root cause here is lifetime capture; shared mutable state and use beyond an owner’s boundary are useful analogies, not interchangeable diagnoses.
#1 Best Overall
Microsoft calls a longer-lived service holding a shorter-lived one a “captive dependency.” Its dependency injection guidelines define the term as a misconfiguration in which “a longer-lived service holds a shorter-lived service captive.” A scoped dependency injected into a singleton is a direct example. The singleton outlives the scope for which the dependency was designed, so request-specific state may persist across requests. A scoped service resolved from the root provider can also effectively become singleton-like: it remains alive until that root provider is disposed.
How to choose a lifetime
- Choose transient when each resolution should receive a fresh instance and the service does not need to share state across resolutions.
- Choose scoped when the same instance should serve one request or another explicitly bounded unit of work, then be released when that scope ends.
- Choose singleton only when sharing one instance for the service-provider lifetime is intentional. Make it thread-safe and avoid retaining request-specific state or dependencies.
Do not make a service singleton just to reduce construction, or transient just because it seems safest. A transient captured by a singleton is no longer effectively short-lived in use. Decide based on the intended sharing boundary, state, and disposal behavior.
Rank #2
How to use scoped work from a singleton or background service
A singleton or hosted background service sometimes needs to perform an operation using scoped dependencies. Create a scope for that operation, resolve the scoped service from the scope, finish the work, and let the scope dispose its services. Do not store the scoped service on the singleton for reuse.
- Inject
IServiceScopeFactoryinto the singleton or hosted service. - For each bounded operation, create a scope with
CreateScope(). - Resolve the scoped service from that scope’s
ServiceProvider, use it only within the operation, then dispose the scope.
This makes the operation, rather than the application-wide singleton, the owner of the scoped instance. Microsoft’s service-lifetime guidance describes creating a scope when a singleton needs scoped work.
Rank #3
Where scoped dependencies belong in ASP.NET Core middleware
Conventional middleware is long-lived. A scoped dependency therefore should not be captured through its constructor. Put that dependency in the middleware’s Invoke or InvokeAsync method, where it can be supplied within the request scope, or use factory-based middleware. See Microsoft’s ASP.NET Core dependency injection guidance for middleware-specific details.
How to catch and diagnose lifetime mistakes
Enable scope validation in development so the built-in provider can catch common cases, such as resolving a scoped service from the root provider or injecting one into a singleton. Validation is a safeguard, not a substitute for understanding who owns an instance and how long it should live.
Rank #4
- Ask who shares it: one resolution, one scope, or the whole service-provider lifetime?
- Check what it retains: a singleton must not hold request-specific scoped state beyond the request.
- Check who ends its life: allow the container and scope to dispose instances they created; do not dispose a container-owned dependency yourself.
- Look for boundary symptoms: state appearing across requests, stale scoped resources, or memory retained until provider shutdown can point to an instance living longer than intended.
Microsoft’s DI guidelines cover disposal and other container-use recommendations. If you need a broader treatment of DI design in C# and .NET, Manning’s Dependency Injection Principles, Practices, and Patterns explores principles, patterns, and anti-patterns.
Recommended Free Tools
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




