# Cannot Resolve Scoped Service From Root Provider in ASP.NET Core: Root Cause and Fix

The `InvalidOperationException: Cannot resolve scoped service from root provider` error is one of the most common failures .NET teams encounter when moving code from local development into production - or when adding background services to an otherwise well-functioning ASP.NET Core application. It surfaces suddenly, blocks startup entirely, and points to a dependency injection lifetime mismatch that is easy to trigger but takes some understanding to fix correctly. The key insight is that the root service provider - the one that lives for the entire application lifetime - cannot create scoped services because doing so would silently extend their lifetime beyond what they were designed for. For developers who want to go deeper on dependency injection lifetime patterns and see how they play out in a full production codebase, the complete implementation with annotated source code is available on [Patreon](https://www.patreon.com/CodingDroplets).

Background jobs and hosted services are the most common trigger for this error, and understanding why they are singletons - and what that means for any service they try to consume - is the first step toward a durable fix. Chapter 12 of the [ASP.NET Core Web API: Zero to Production course](https://aspnetcoreapi.codingdroplets.com/) covers this exact scenario inside a full production codebase, walking through `IServiceScopeFactory`, the `BackgroundService` lifetime model, and the Outbox pattern that depends on it - all with source code you can run immediately.

[![ASP.NET Core Web API: Zero to Production](https://newsletter.codingdroplets.com/images/aspnet-core-api-course-banner-1.jpg align="center")](https://aspnetcoreapi.codingdroplets.com/)

## Why the Root Provider Refuses to Resolve Scoped Services

Every ASP.NET Core application has a root `IServiceProvider` that is created once when the host starts and is disposed only when the host shuts down. Its lifetime spans the entire application. Scoped services, by contrast, are designed to live for the duration of a single request - or, more precisely, for the duration of a single `IServiceScope`. They carry assumptions about their own lifetime: an `ApplicationDbContext` registered as scoped, for example, is expected to be created per request, track changes for that request, and then be disposed cleanly.

When the root provider attempts to resolve a scoped service directly - without creating a scope first - the DI container throws `InvalidOperationException: Cannot resolve scoped service 'YourServiceType' from root provider`. This is a deliberate safety check. If the root provider were allowed to resolve scoped services, those services would effectively become singletons for the application's lifetime, leading to shared state across requests, undisposed database connections, and subtle data consistency bugs.

In development, this check is enforced by default because `ValidateScopes` is enabled when `ASPNETCORE_ENVIRONMENT` is `Development`. In production, `ValidateScopes` defaults to false - which means the same code that throws in development might silently create a scoped-service-as-singleton in production, only manifesting as slow memory growth or stale data. This is why fixing the root cause - not just suppressing the validation - matters.

## How Does This Actually Happen in Practice?

### Hosted Services and BackgroundService

The most frequent trigger is an `IHostedService` or `BackgroundService` implementation that tries to inject a scoped service via constructor injection. Both `IHostedService` and `BackgroundService` implementations are registered as singletons - `AddHostedService<T>()` registers them with singleton lifetime by design, because a background service lives and runs for the entire application lifetime.

If `ExecuteAsync` or `StartAsync` attempts to call into an `ApplicationDbContext`, a repository interface, or any other scoped service that was injected through the constructor, the DI container sees the root provider resolving a scoped dependency on behalf of a singleton. The result is the exception - or worse, a silent lifetime promotion if scope validation is off.

The same pattern appears in middleware constructors. Middleware is instantiated once, not per request. Constructor-injected services in middleware are resolved from the root provider. Any scoped service injected there inherits singleton lifetime for the duration of the application.

### Singleton Services That Consume Scoped Dependencies

A singleton service that declares a dependency on a scoped service in its constructor triggers the same failure. Because the singleton is resolved once from the root provider, its scoped dependency must also be resolved by the root provider - which the container refuses to do when scope validation is enabled.

This pattern is easy to introduce accidentally when developers change a service's registration from scoped to singleton (for performance or architectural reasons) without auditing its dependency tree.

## The Correct Fix: IServiceScopeFactory

The canonical solution is to inject `IServiceScopeFactory` instead of the scoped service itself. `IServiceScopeFactory` is registered as a singleton and is safe to resolve from the root provider. Within `ExecuteAsync` or any method that needs a scoped service, you create a scope explicitly, resolve the service from that scope's provider, use it, and dispose the scope when done.

```csharp
public class DataSyncBackgroundService(IServiceScopeFactory scopeFactory) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using var scope = scopeFactory.CreateScope();
            var repo = scope.ServiceProvider.GetRequiredService<IDataSyncRepository>();
            await repo.SyncPendingRecordsAsync(stoppingToken);

            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }
}
```

The `using` ensures the scope - and every service resolved from it - is disposed at the end of each iteration. This is the production-correct pattern. The `DbContext` tied to the repository lives for exactly one sync operation, then is released cleanly.

For middleware, the fix is different: instead of injecting scoped services via the constructor, inject them through the `Invoke` or `InvokeAsync` method parameters. ASP.NET Core resolves middleware method parameters per request, so services injected there are correctly resolved from the per-request scope.

```csharp
public class AuditMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(HttpContext context, IAuditService auditService)
    {
        await auditService.LogRequestAsync(context);
        await next(context);
    }
}
```

`IAuditService` here is resolved from the request scope, not the root provider, even though the middleware itself is a singleton.

## When ValidateScopes Is the Messenger, Not the Problem

A common reaction to this error is to disable scope validation:

```csharp
builder.Host.UseDefaultServiceProvider(options =>
{
    options.ValidateScopes = false; // ❌ This hides the bug, it does not fix it
});
```

This makes the exception disappear - but the underlying lifetime mismatch remains. Scoped services resolved from the root provider will accumulate state, hold open database connections, and leak memory. The right response to this exception is to treat it as a useful signal and fix the registration or resolution pattern, not to silence the validator.

A helpful addition during development is `ValidateOnBuild`, which catches lifetime mismatches at startup rather than at runtime:

```csharp
builder.Host.UseDefaultServiceProvider(options =>
{
    options.ValidateScopes = builder.Environment.IsDevelopment();
    options.ValidateOnBuild = true; // ✅ Catches issues at startup in all environments
});
```

`ValidateOnBuild = true` is safe to run in production - it adds a small startup cost but eliminates an entire class of DI misconfiguration from reaching live requests.

## Auditing a Production System for This Problem

If `ValidateScopes` was disabled in production and the application has been running for some time, the fix is not just to re-enable validation. The service dependency tree needs an audit:

1.  Identify all singleton registrations (including `IHostedService`, `BackgroundService`, middleware, and any manually registered singletons).
    
2.  For each singleton, inspect its constructor dependencies and their registered lifetimes.
    
3.  Any scoped or transient dependency of a singleton is a candidate for the `IServiceScopeFactory` pattern.
    
4.  Re-enable scope validation in staging first to surface issues without risking production.
    

The [ASP.NET Core DI Lifetimes decision guide](https://codingdroplets.com/aspnet-core-di-lifetimes-singleton-scoped-transient-enterprise-decision-guide) on this blog covers the broader rules for choosing lifetimes correctly. If your team is working with hosted services and background job patterns specifically, the [Background Services in .NET 10 enterprise guide](https://codingdroplets.com/background-services-dotnet-10-ihostedservice-vs-backgroundservice-enterprise-guide) is the right companion.

## Prevention Going Forward

**Register services with the correct lifetime from the start.** Before registering any service as a singleton, ask whether any of its dependencies are scoped or transient. If they are, either redesign the dependency graph or plan to use `IServiceScopeFactory` from the beginning.

**Enable** `ValidateOnBuild` **in all environments.** The startup cost is negligible and the protection is significant.

**Use scoped services through scoped lifetimes.** Background services should always create a new scope per unit of work, not hold one open for the entire application lifetime. A scope per iteration of the background loop is the correct model.

**Test background services in integration tests using real DI containers.** `WebApplicationFactory<TProgram>` creates a real host with a real DI container. Background service DI mismatches show up immediately when the test host starts.

## FAQ

### What does "Cannot resolve scoped service from root provider" mean?

It means the ASP.NET Core DI container detected an attempt to resolve a service with scoped lifetime directly from the root `IServiceProvider`, which lives for the entire application. Scoped services are designed for per-request lifetimes. Allowing them to be resolved from the root would turn them into effective singletons, causing shared state, undisposed resources, and data integrity problems. The exception is a protective guard against this class of bug.

### Why does this error only appear in development and not in production?

By default, ASP.NET Core enables `ValidateScopes` when the environment is `Development` and disables it in `Production`. In production, resolving a scoped service from the root provider does not throw - instead, the service is silently promoted to singleton lifetime. This can manifest as slow memory growth, database connection exhaustion, or stale data. The fix is to resolve the misconfiguration rather than rely on the absence of the exception.

### How do I use a scoped service inside a BackgroundService?

Inject `IServiceScopeFactory` via the `BackgroundService` constructor (it is a singleton and safe to inject). Inside `ExecuteAsync`, call `scopeFactory.CreateScope()` to create a new scope, resolve your scoped service from `scope.ServiceProvider`, perform the work, and dispose the scope with a `using` block. This gives each background job iteration its own clean DI scope, equivalent to a per-request scope in a normal API endpoint.

### Can I use scoped services inside ASP.NET Core middleware?

Yes, but not via constructor injection. Middleware classes are singletons - constructor-injected dependencies are resolved once from the root provider. To use scoped services in middleware, add them as parameters to the `Invoke` or `InvokeAsync` method. ASP.NET Core resolves method-injected dependencies per request from the correct request scope.

### What is ValidateOnBuild and should I enable it in production?

`ValidateOnBuild = true` instructs the DI container to validate the entire service registration graph when the application host is built - at startup, before any requests are served. It catches lifetime mismatches and missing registrations early. It adds a small startup overhead but zero runtime overhead for normal request processing. Enabling it in production is safe and is recommended as a defence-in-depth measure against DI misconfiguration.

### What is the difference between IServiceScopeFactory and IServiceProvider for this problem?

`IServiceScopeFactory` is registered as a singleton and is designed exactly for the use case of creating scopes on demand from long-lived services. `IServiceProvider` in a background service or singleton is the root provider - calling `GetService` on it directly is the source of the problem. The `IServiceScopeFactory.CreateScope()` method returns an `IServiceScope` whose `ServiceProvider` is a properly bounded child provider, not the root. Always use `IServiceScopeFactory` when a singleton needs to resolve scoped dependencies.

### Does this problem occur with IHostedService as well as BackgroundService?

Yes. `IHostedService` implementations registered via `AddHostedService<T>()` are singletons regardless of whether they extend `BackgroundService` or implement the interface directly. The same restriction applies: any scoped dependency must be obtained through a freshly created `IServiceScope`, not injected into the constructor.

* * *

## About the Author

**Celin Daniel** is Co-founder of Coding Droplets with 13+ years of hands-on experience building, shipping, and operating .NET and ASP.NET Core systems in production. The guidance here comes from real projects and production incidents, not theory.

- Website: [codingdroplets.com](https://codingdroplets.com/)
- GitHub: [github.com/codingdroplets](http://github.com/codingdroplets/)
- YouTube: [youtube.com/@CodingDroplets](https://www.youtube.com/@CodingDroplets)
