gotchacsharpdotnetCritical
Dependency injection: Captive dependency — scoped service injected into singleton
Viewed 0 times
captive dependencyscoped singletonIServiceScopeFactoryValidateScopesDbContext singleton
Error Messages
Problem
Injecting a scoped service (e.g., DbContext) into a singleton causes it to be captured for the lifetime of the singleton. In a web app this means a single DbContext instance is shared across all requests, leading to thread safety violations and stale data.
Solution
Never inject scoped services into singletons directly. Instead inject IServiceScopeFactory and create a scope when needed:
Enable runtime validation in development:
public class MySingleton
{
private readonly IServiceScopeFactory _scopeFactory;
public MySingleton(IServiceScopeFactory scopeFactory)
=> _scopeFactory = scopeFactory;
public async Task DoWorkAsync()
{
using var scope = _scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Items.ToListAsync();
}
}Enable runtime validation in development:
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = true; // throw on captive dependency
options.ValidateOnBuild = true; // validate graph at startup
});Why
DI container resolves scoped instances per scope (usually per HTTP request). A singleton holds a reference to the scope that was active at construction time, which may have been disposed, or extends that scope indefinitely instead of creating a fresh one per request.
Gotchas
- ValidateScopes is true by default in development but false in production — a bug can hide until prod
- IHttpContextAccessor is scoped; injecting it into a singleton is a common mistake
- Background services (IHostedService) run as singletons — always use IServiceScopeFactory inside them
Revisions (0)
No revisions yet.