Cross-wired scoped services don't support nested scoping?
See original GitHub issueI’ve encountered a situation in which cross-wired scoped service (e.g. DbContext
) needs to be isolated in a nested scope via SimpleInjector’s nested scoping capabilities.
I thought it is supported out-of-the-box, as SimpleInjector preserves the scope from NetCore’s cross-wired service. It is also preserved in my case as each HTTP request ends up having a new instance of it, for example. However, it’s not acting like a scoped instance when trying to do some work inside the nested scope created via AsyncScopedLifestyle.BeginScope
method. In that case, the same service acts as a “singleton” as I’m getting the same instance of the service.
I couldn’t find more thorough documentation about SimpleInjector nested scoping in conjunction with cross-wired scoped service (from IServiceCollection
). So, would it be possible to give more insights into internal specifics or point me somewhere else, as it feels/sounds like a bug at this moment, although I guess it’s not the case.
Here is the setup:
public interface IScopedService { }
public class ScopedService : IScopedService { }
public class Startup
{
private readonly Container _container = new Container();
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddScoped<IScopedService, ScopedService>();
services.AddSimpleInjector(_container, opts =>
{
opts.AddAspNetCore().AddControllerActivation();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSimpleInjector(_container);
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
As I found that when building API, here is a test controller used to show the behavior:
[ApiController]
[Route("")]
public class TestController : ControllerBase
{
private readonly Container _container;
public TestController(Container container)
{
_container = container;
}
[HttpGet]
public async Task<bool> Get()
{
Debug.WriteLine($"Service : {_container.GetInstance<IScopedService>().GetHashCode()}");
using (AsyncScopedLifestyle.BeginScope(_container))
{
Debug.WriteLine($"Service Scope 1: {_container.GetInstance<IScopedService>().GetHashCode()}");
using (AsyncScopedLifestyle.BeginScope(_container))
{
Debug.WriteLine($"Service Scope 1.1: {_container.GetInstance<IScopedService>().GetHashCode()}");
}
}
using (AsyncScopedLifestyle.BeginScope(_container))
{
Debug.WriteLine($"Service Scope2: {_container.GetInstance<IScopedService>().GetHashCode()}");
}
using (var scope = AsyncScopedLifestyle.BeginScope(_container))
{
Debug.WriteLine($"Service Scope 3: {scope.GetInstance<IScopedService>().GetHashCode()}");
}
return true;
}
}
This is the output which I get:
Service : 47441805 Service Scope 1: 47441805 Service Scope 1.1: 47441805 Service Scope2: 47441805 Service Scope 3: 47441805
However, if I register the same service with Lifestyle.Scoped
in SimpleInjector, I get the expected behavior and the following output:
Service : 6044116 Service Scope 1: 59817589 Service Scope 1.1: 48209832 Service Scope2: 5773521 Service Scope 3: 54135081
Usually, I’d just relocate registration to SimpleInjector, however what if we can’t do that easily, let’s say I want to take advantage of AddDbContext
extension method to register DbContext
. So, is there a way to configure/integrate those containers differently to allow nesting of cross-wired scoped service?
I’m using latest ASP.NET Core (3.1) and SimpleInjector (4.9)
Issue Analytics
- State:
- Created 4 years ago
- Comments:8
This is amazing, with such overload in place all the confusion (I had) around cross-wiring and scoping is gone, thank you again for all the effort!
That absolutely makes sense and was already considering such construct.