Thursday, 11 December 2025

Using Scoped Services From Singletons in ASP.NET Core

 Did you ever need to inject a scoped service into a singleton service?

I often need to resolve a scoped service, like the EF Core DbContext, in a background service.

Another example is when you need to resolve a scoped service in ASP.NET Core middleware.

If you ever tried this, you were probably greeted with an exception similar to this one:

System.InvalidOperationException: Cannot consume scoped service 'Scoped' from singleton 'Singleton'.

Today, I'll explain how you can solve this problem and safely use scoped services from within singletons in ASP.NET Core.

ASP.NET Core Service Lifetimes

ASP.NET Core has three service lifetimes:

  • Transient
  • Singleton
  • Scoped

Transient services are created each time they're requested from the service container.

Scoped services are created once within the scope's lifetime. For ASP.NET Core applications, a new scope is created for each request. This is how you can resolve scoped services within a given request.

ASP.NET Core applications also have a root IServiceProvider used to resolve singleton services.

So, what can we do if resolving a scoped service from a singleton throws an exception?

The Solution - IServiceScopeFactory

What if you want to resolve a scoped service inside a background service?

You can create a new scope (IServiceScope) with its own IServiceProvider instance. The scoped IServiceProvider can be used to resolve scoped services. When the scope is disposed, all disposable services created within that scope are also disposed.

Here's an example of using the IServiceScopeFactory to create a new IServiceScope. We're using the scope to resolve the ApplicationDbContext, which is a scoped service.

The BackgroundJob is registered as a singleton when calling AddHostedService<BackgroundJob>.

public class BackgroundJob(IServiceScopeFactory serviceScopeFactory)
    : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using IServiceScope scope = serviceScopeFactory.CreateScope();

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

        // Do some background processing with the EF database context.
        await DoWorkAsync(dbContext);
    }
}

Scoped Services in Middleware

What if you want to use a scoped service in ASP.NET Core middleware?

Middleware is constructed once per application lifetime.

If you try injecting a scoped service, you'll get an exception:

System.InvalidOperationException: Cannot resolve scoped service 'Scoped' from root provider.

There are two ways to get around this.

First, you could use the previous approach with creating a new scope using IServiceScopeFactory. You'll be able to resolve scoped services. But, they won't share the same lifetime as the other scoped service in the same request. This could even be a problem depending on your requirements.

Is there a better way?

Middleware allows you to inject scoped services in the InvokeAsync method. The injected services will use the current request's scope, so they'll have the same lifetime as any other scoped service.

public class ConventionalMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(
        HttpContext httpContext,
        IMyScopedService scoped)
    {
        scoped.DoSomething();

        await _next(httpContext);
    }
}

IServiceScopeFactory vs. IServiceProvider

You might see examples using the IServiceProvider to create a scope instead of the IServiceScopeFactory.

What's the difference between these two approaches?

The CreateScope method from IServiceProvider resolves an IServiceScopeFactory instance and calls CreateScope() on it:

public static IServiceScope CreateScope(this IServiceProvider provider)
{
    return provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
}

So, if you want to use the IServiceProvider directly to create a scope, that's fine.

However, the IServiceScopeFactory is a more direct way to achieve the desired result.

Summary

Understanding the difference between Transient, Scoped, and Singleton lifetimes is crucial for managing dependencies in ASP.NET Core applications.

The IServiceScopeFactory provides a solution when you need to resolve scoped services from singletons. It allows you to create a new scope, which you can use to resolve scoped services.

In middleware, we can inject scoped services into the InvokeAsync method. This also ensures the services use the current request's scope and lifecycle.


Not only the repository pattern in the .Net core, but IServiceScopeFactory also solves similar requirements


In .NET Core, dependency injection (DI) is a core feature for managing and controlling the lifecycle of dependencies (services). However, in some cases, you need more control over how long services live, especially for services that are scoped. This is where the IServiceScopeFactory comes in.

Embark on a journey of continuous learning and exploration with DotNet-FullStack-Dev. Uncover more by visiting our https://dotnet-fullstack-dev.blogspot.com reach out for further information.

Join Medium for free to get updates from this writer.

IServiceScopeFactory is an interface that helps create a scoped lifetime for services manually. Scoped services are created for each request in a web application, but sometimes you need to create these scopes explicitly outside of the typical request pipeline. IServiceScopeFactory allows you to do this, providing flexibility in managing service lifetimes.

When to Use IServiceScopeFactory

You typically use IServiceScopeFactory when:

  • You need to resolve scoped services outside of the standard HTTP request pipeline.
  • You are working with background tasks or hosted services where there is no request context but you still need scoped dependencies.
  • You want to explicitly control the lifetime of services, such as in multi-threaded scenarios or long-running processes.

How IServiceScopeFactory Works

  • Scope: A service scope is an isolated container for scoped services. When the scope is created, the services inside the scope have a limited lifetime, and they are disposed of when the scope ends.
  • Service Resolution: Services inside the scope are resolved through the service provider of that scope. This allows you to handle scoped services independently from the main DI container.

Example: Using IServiceScopeFactory in a Background Task

Consider a scenario where you need to run background tasks that depend on a scoped service, such as accessing the database. In a background task, there is no HTTP request, but you still need to create a scope manually to resolve the scoped services.

Step 1: Define a Scoped Service

public interface IItemService
{
Task ProcessItemsAsync();
}

public class ItemService : IItemService
{
private readonly ApplicationDbContext _dbContext;

public ItemService(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}

public async Task ProcessItemsAsync()
{
// Fetch and process items from the database
var items = await _dbContext.Items.ToListAsync();
// Process items...
}
}

Here, ItemService is a scoped service that depends on ApplicationDbContext, which is also scoped.

Step 2: Use IServiceScopeFactory in a Background Task

Let’s assume that you need to process items in the background periodically.

public class ItemProcessingBackgroundService : BackgroundService
{
private readonly IServiceScopeFactory _serviceScopeFactory;

public ItemProcessingBackgroundService(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var itemService = scope.ServiceProvider.GetRequiredService<IItemService>();

// Call the scoped service to process items
await itemService.ProcessItemsAsync();
}

// Wait for some time before running the next iteration
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}

In this example:

  • IServiceScopeFactory is injected into the background service.
  • The ExecuteAsync method creates a scope for each iteration of the background task using CreateScope().
  • Inside the scope, the IItemService (which is a scoped service) is resolved and used.
  • The scope ensures that the scoped services (like ApplicationDbContext) are created and disposed of correctly with each iteration.

Benefits of Using IServiceScopeFactory

  • Control over Scope Lifetime: You have precise control over when scoped services are created and disposed.
  • Resource Management: Scoped services are disposed automatically when the scope is disposed, ensuring proper cleanup and reducing memory leaks.
  • Non-Request Scenarios: It allows scoped services to be used in non-request-driven processes like background services, hosted services, or multi-threaded tasks.

Common Use Cases

  1. Background Services: When running long-lived background services that need to interact with scoped services, such as database access.
  2. Hosted Services: When implementing IHostedService or BackgroundService, you might need to use scoped services outside of the HTTP request pipeline.
  3. Multi-Threaded Scenarios: When running operations in parallel where each thread requires its own scope for dependencies.
  4. Manually Managing Scopes: In complex scenarios where scope lifetime needs to be managed independently of the request/response lifecycle.



No comments:

Post a Comment

Recent Post

Using Scoped Services From Singletons in ASP.NET Core