-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Description
In the performance documentation for ASP.NET Core there is an example showing the use of IServiceScopeFactory
from request services but making use of that factory after the request services set has been disposed.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory
serviceScopeFactory)
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
using (var scope = serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();
context.Contoso.Add(new Contoso());
await context.SaveChangesAsync();
}
});
return Accepted();
}
This documentation implies that the IServiceScopeFactory
that is provided by request services is a singleton, or at least that it always provides service scopes that come from the root application container rather than being hierarchical. If this wasn't the case, then the parent scope that the IServiceScopeFactory
came from would already have been disposed and you wouldn't be able to create a nested scope.
Basically, the documentation shows this:
- Root
IServiceProvider
- Request services scope
- Scope created by
IServiceScopeFactory
When, if scopes are hierarchical, it'd be more like:
- Root
IServiceProvider
- Request services scope
- Scope created by
IServiceScopeFactory
(which couldn't happen, because request services has already been disposed)
- Scope created by
- Request services scope
There is no DI specification test indicating this all-scopes-must-come-from-the-root behavior, so either there's a documentation problem or there's a breaking change that will end up having to get introduced into the specification tests to make sure every conforming container implementation adheres to this two-level-only scope mechanism.
While I recognize that the context here is ASP.NET, since the DI bits are in this repo it seemed reasonable to start here. The behavior is more about what's expected from the DI implementation than it is about how ASP.NET consumes it.
Possible overlap with dotnet/aspnetcore#31478 where there is some discussion about the assumption IServiceScopeFactory
is a singleton, but again, without any spec tests to enforce that and without discussing that decision with the community. This is only how the Microsoft container works, not how every conforming container works.
Reproduction Steps
Implement an ASP.NET app (or, I guess, a Console app) that performs effectively this sort of multi-threaded handling of scopes:
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory
serviceScopeFactory)
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
using (var scope = serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();
context.Contoso.Add(new Contoso());
await context.SaveChangesAsync();
}
});
return Accepted();
}
Under the Microsoft container, this passes. Now back it with Autofac, which enforces hierarchical scopes. You'll fail because the request services scope has been disposed and you can't create a new child scope from a scope that's disposed.
Expected behavior
I expect the documentation to show an example that works with the default container and with conforming containers that adhere to the specification tests.
Actual behavior
Microsoft DI works, other conforming containers may or may not work despite passing the spec tests.
Regression?
Not a regression that I'm aware of.
Known Workarounds
Folks using other containers will have to use backing-container-specific workarounds. For example, instead of injecting an IServiceScopeFactory
, folks wanting to do this with Autofac will need to hold a global static reference to the root container and manually create a child scope from that root container.
Configuration
The configuration, in this case, doesn't matter, though I'm on .NET 6 and the documentation is for .NET 6 that I linked.
Other information
The documentation should show something that works with both Microsoft and other conforming container providers. If that means that documentation needs to be removed because it's wrong, remove it. If it means introducing new requirements for conforming containers, that's definitely something to get the rest of the community involved in because it's going to break some folks who don't make this assumption.