Using Scoped Services From Singletons in ASP.NET Core

Using Scoped Services From Singletons in ASP.NET Core

3 min read ·

Thank you to our sponsors who keep this newsletter free to the reader:

Become a Postman master with Postman Intergalactic sessions. Test your APIs with ease and collaborate on API development with workspaces. Get Postman for FREE!

Introducing Shesha, a brand new, open-source, low-code framework for .NET developers. Create business applications faster and with >80% less code! Learn more here.

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.

Thanks for reading, and I'll see you next week!


Whenever you're ready, there are 4 ways I can help you:

  1. (COMING SOON) REST APIs in ASP.NET Core: You will learn how to build production-ready REST APIs using the latest ASP.NET Core features and best practices. It includes a fully functional UI application that we'll integrate with the REST API. Join the waitlist!
  2. Pragmatic Clean Architecture: Join 3,150+ students in this comprehensive course that will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture.
  3. Modular Monolith Architecture: Join 1,050+ engineers in this in-depth course that will transform the way you build modern systems. You will learn the best practices for applying the Modular Monolith architecture in a real-world scenario.
  4. Patreon Community: Join a community of 1,000+ engineers and software architects. You will also unlock access to the source code I use in my YouTube videos, early access to future videos, and exclusive discounts for my courses.
  5. Promote yourself to 58,000+ subscribers by sponsoring this newsletter.

Become a Better .NET Software Engineer

Join 58,000+ engineers who are improving their skills every Saturday morning.