ASP.NET Core Best Practices

Chapter 2 · Practice 1 of 4

Never inject a scoped service into a singleton

From Dependency injection and configuration

Why it matters

Every registration has a lifetime:

LifetimeCreatedTypical use
TransientEvery time it is requestedLightweight, stateless helpers
ScopedOnce per requestDbContext, unit of work, per-request state
SingletonOnce per appCaches, clients, configuration

The docs warn: do not resolve a scoped service from a singleton. The scoped service then lives as long as the singleton, a captive dependency, so one request's DbContext and data leak into later requests.

By default, in the Development environment, the container throws when a service resolves one with a shorter lifetime. Do not turn that validation off. When a singleton or a background service needs scoped services, create a scope with IServiceScopeFactory.

Singletons must also be thread safe, since every request shares them.

Example

C#
public sealed class NightlyReport(IServiceScopeFactory scopes) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        await using var scope = scopes.CreateAsyncScope();
        var db = scope.ServiceProvider.GetRequiredService<ShopDbContext>();
        await db.Orders.Where(o => o.PlacedAt < DateTime.UtcNow.AddDays(-30))
            .ExecuteDeleteAsync(ct);
    }
}