HomeNikola Knezevic

In this article

Banner

Scoped Services in Singleton in .NET Core

26 Feb 2026
4 min

Sponsor Newsletter

Dependency Injection (DI) is a cornerstone of ASP.NET Core applications.

Dependency Injection is a software design pattern that implements the Inversion of Control (IOC) principle, allowing objects to receive their dependencies from the outside instead of creating them themselves.

While it is a mature part of .NET ecosystem, there are still some missundarstanding that I will try to adress in todays blog post.

Service lifetimes

In ASP.NET Core, every service you register has a lifetime that controls how long an instance of the service lives. There are three main lifetimes:

  • Transient - A new instance is created every time the service is requested.
  • Scoped - One instance is created per HTTP request.
  • Singleton - A single instance is created once for the entire application lifetime.

While it's easy to understand transient and singleton lifetimes, scoped often couses confusion, especially inside singleton service.

We usually say that scoped is resolved once per HTTP request, but that's not entirely true.

Scope is like a boundary that defines how long a scoped instance lives, for example each HTTP request automatically creates a new scope.

However, you can also create your own scopes manually when needed.

The problem

Consider a background job that runs every few seconds to close obsolete orders.

To access the database, we need a DbContext, which is registered as a scoped service in the DI container.

If we try to inject DbContext directly into the background service (which is a Singleton), we will immediately get an exception:

csharp
public class OrderProcessJob(ApplicationDbContext dbContext) : BackgroundService
{
    private readonly TimeSpan _period = TimeSpan.FromSeconds(5);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using PeriodicTimer timer = new PeriodicTimer(_period);

        while (await timer.WaitForNextTickAsync(stoppingToken))
        {
            var orders = await dbContext.Orders
                .Where(x => x.Status == OrderStatus.Submitted)
                .ToListAsync(stoppingToken);

            orders.ForEach(order => order.Process());

            await dbContext.SaveChangesAsync();
        }
    }
}
shell
InvalidOperationException: Cannot consume scoped service 'WebApi.Database.ApplicationDbContext' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.

This happens because the background service is created once for the entire application lifetime, while DbContext is scoped and must live within a defined scope.

The solution

The solution is straightforward, just create a scope manually.

By creating a custom service scope, we can safely resolve scoped services inside a Singleton:

csharp
public class OrderProcessJob(IServiceScopeFactory scopeFactory) : BackgroundService
{
    private readonly TimeSpan _period = TimeSpan.FromSeconds(5);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using PeriodicTimer timer = new PeriodicTimer(_period);

        while (await timer.WaitForNextTickAsync(stoppingToken))
        {
            using IServiceScope scope = scopeFactory.CreateScope();

            await using var dbContext = scope.ServiceProvider
                .GetRequiredService<ApplicationDbContext>();

            var orders = await dbContext.Orders
                .Where(x => x.Status == OrderStatus.Submitted)
                .ToListAsync(stoppingToken);

            orders.ForEach(order => order.Process());

            await dbContext.SaveChangesAsync();
        }
    }
}

By injecting IServiceScopeFactory into the singleton background service on each iteration, it will create a new scope with CreateScope().

With the new scope we can resolve DbContext by using ServiceProvider in scope with the GetRequiredService method.

Always make sure to use a using block so the scope is disposed correctly.

IServiceProvider alternative

Another way to create a scope inside a Singleton is to inject IServiceProvider instead of IServiceScopeFactory.

This works because IServiceProvider.CreateScope() internally resolves IServiceScopeFactory and calls CreateScope() on your behalf.

Both approaches are valid and fully supported.

I personally prefer injecting IServiceScopeFactory.

Conclusion

Scoped does not mean “per HTTP request.” It means “per scope.”

HTTP requests automatically create scopes for us, which is why scoped services typically behave as “per request” dependencies in web applications. However, outside of the request pipeline we must create scopes manually.

Both IServiceScopeFactory and IServiceProvider are valid and supported approaches.

If you want to check out examples I created, you can find the source code here:

Source Code

I hope you enjoyed it, subscribe and get a notification when a new blog is up!

Subscribe

Stay tuned for valuable insights every Thursday morning.