Dependency Injection Lifetimes

2 min read
Mid-level2 min read
Rapid overview

Dependency Injection Lifetimes

TL;DR

šŸ” Tip: Never capture a scoped service inside a singleton—inject IServiceScopeFactory instead and create a scope per operation.

How it works

LifetimeDescriptionCommon RegistrationBest For
TransientA new instance is created every time it’s requested.AddTransient<IService, Service>()Lightweight, stateless work such as formatters, command objects, or EF Core DbContext factories.
ScopedOne instance per HTTP request (scope).AddScoped<IService, Service>()Request-based state such as per-request caches, unit-of-work patterns, or EF Core DbContext.
SingletonOne instance for the entire application lifetime.AddSingleton<IService, Service>()Cross-request services like configuration, logging, caching, or HTTP clients created via IHttpClientFactory.

Quick recall Q&A

Q: When do you choose transient over scoped?

Use transient for stateless, lightweight services or short-lived operations (formatters, handlers). Use scoped when the service holds per-request state or depends on scoped services like DbContext.

Q: Why does capturing a scoped service in a singleton cause issues?

The singleton outlives the request scope, so it holds onto disposed or cross-request state, leading to race conditions, memory leaks, or ObjectDisposedException.

Q: How do you use scoped services from a singleton safely?

Inject IServiceScopeFactory, create a scope when needed, resolve the scoped service inside it, and dispose the scope afterward.

Q: What lifetime should HttpClient instances have?

Use IHttpClientFactory (singleton-managed) to create clients per use, which internally pools handlers and avoids socket exhaustion.

Q: Can singletons depend on transients?

Yes, but the transient effectively becomes a singleton because the DI container creates it once for the singleton. Prefer injecting interfaces with the right lifetime semantics.

Q: How do lifetimes work in background services?

Hosted services run outside HTTP scopes. If they need scoped services, create scopes manually per iteration to avoid cross-thread leaks.

Q: How do you test scoped services without ASP.NET hosting?

Create a ServiceScope via the provider in tests, resolve scoped services, and dispose the scope when done. This mimics per-request behavior.

Q: What happens if you register DbContext as singleton?

It becomes shared across requests, causing threading issues, stale state, and memory leaks. EF contexts must be scoped or transient.

Q: How do you ensure deterministic disposal for transients?

The container disposes transients when the scope disposing them ends. If they hold unmanaged resources, ensure they are resolved and disposed within a scope.

Q: What about custom lifetimes?

You can implement IServiceScopeFactory or use keyed services, but keep the mental model simple—most cases are solved with transient/scoped/singleton.

See also