Background and motivation
In modern .NET applications, developers heavily rely on dependency injection (DI) scopes to manage the lifetime of resources per operation (e.g., handling a web request, processing a message queue item, or executing a background task). While ASP.NET Core provides IHttpContextAccessor to access the current HTTP context and its associated RequestServices ambiently, .NET lacks a generic, runtime-agnostic mechanism to access the active IServiceProvider scope outside of web environments.
This limitation frequently causes architectural pain points in non-web applications (such as Worker Services, Console Apps, or any Generic Host), or when writing infrastructure libraries that span both web and non-web contexts. For example:
- Cross-Lifetime Consumption: A Singleton service needs to process contextual data or interact with a stateful Scoped service, but it is invoked from deep within a scoped execution pipeline where passing
IServiceProvider explicitly through method signatures is impossible or highly intrusive.
- Library Authoring: Infrastructure components need to ambiently fetch services from the active scope without forcing application code to manually flow the container down the call stack.
Providing a built-in IScopedServiceProviderAccessor powered by AsyncLocal<T> resolves these issues. It standardizes ambient scope tracking natively within Microsoft.Extensions.DependencyInjection, bringing parity to non-web hosting environments and eliminating the need for developers to repeatedly implement custom, error-prone thread-local or async-local tracking wrappers.
API Proposal
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Provides access to the current scope <see cref="IServiceProvider"/>, if one is available.
/// </summary>
public interface IScopedServiceProviderAccessor
{
/// <summary>
/// Gets or sets the current scope <see cref="System.IServiceProvider"/>. Returns <see langword="null" /> if there is no active scope.
/// </summary>
IServiceProvider? ServiceProvider { get; set; }
}
API Usage
public class SingletonService(IScopedServiceProviderAccessor scopeAccessor)
{
private readonly IScopedServiceProviderAccessor _scopeAccessor = scopeAccessor;
public void DoSomething()
{
// Effortlessly retrieve the caller's scoped provider instance
var scopedProvider = _scopeAccessor.ServiceProvider;
if (scopedProvider is not null)
{
// Resolve scoped service specific to this call execution flow
var currentUser = scopedProvider.GetRequiredService<UserContext>();
}
}
}
Alternative Designs
No response
Risks
No response
Background and motivation
In modern .NET applications, developers heavily rely on dependency injection (DI) scopes to manage the lifetime of resources per operation (e.g., handling a web request, processing a message queue item, or executing a background task). While ASP.NET Core provides
IHttpContextAccessorto access the current HTTP context and its associatedRequestServicesambiently, .NET lacks a generic, runtime-agnostic mechanism to access the activeIServiceProviderscope outside of web environments.This limitation frequently causes architectural pain points in non-web applications (such as Worker Services, Console Apps, or any Generic Host), or when writing infrastructure libraries that span both web and non-web contexts. For example:
IServiceProviderexplicitly through method signatures is impossible or highly intrusive.Providing a built-in
IScopedServiceProviderAccessorpowered byAsyncLocal<T>resolves these issues. It standardizes ambient scope tracking natively withinMicrosoft.Extensions.DependencyInjection, bringing parity to non-web hosting environments and eliminating the need for developers to repeatedly implement custom, error-prone thread-local or async-local tracking wrappers.API Proposal
API Usage
Alternative Designs
No response
Risks
No response