Disclaimer
I would like to preface this discussion by stating that trying to use scoped services inside of singletons (or using any short-lived service inside of a longer-lived service for that matter!) is generally a bad idea. There's a reason why most dependency injection (DI) containers try to stop you from doing it. Trying to use scoped services inside of singletons can lead to what's known as Captive Dependencies, which can cause all sorts of nasty bugs and memory leaks.
If you find yourself in a situation where you are trying to inject a scoped service into a singleton, that's generally a code smell and you should seriously consider refactoring your services to avoid that dependency.
Nevertheless, there are sometimes legitimate reasons to use scoped services inside singletons. If you believe this to be true of your use case, then please read on.
Introduction
Have you ever tried injecting a service into another service, but get the following exception?:
InvalidOperationException: Cannot resolve scoped service 'IMyScopedService' from root provider.
If so, this is a good indication that you might be trying to inject a scoped service into a singleton service. The exception appears because the DI container is trying to protect you from Captive Dependencies (see the disclaimer above). While it is generally good that the DI container tries to stop you from doing such things, it is sometimes necessary to do so.
I recently came across this when trying to create a hosted service in ASP.NET Core. A hosted service allows you to create long running background task, and it essentially behaves like a singleton service (with a few minor caveats).
Some of my background tasks need to make use of other services, most of which are transient/scoped. However, when I tried injecting these services into the consructor of my hosted service, I got an exception similar to the one above.
The Problem
The ASP.NET Core DI container has a root IServiceProvider
which is used to resolve singleton services. For scoped services, the container must first create a new scope, and each scope will have it's own IServiceProvider
. Scoped services can only be accessed from the IServiceProvider
within their own scope, and not from the root IServiceProvider
.
The Solution
To be able to use scoped services within a singleton, you must create a scope manually. A new scope can be created by injecting an IServiceScopeFactory
into your singleton service (the IServiceScopeFactory
is itself a singleton, which is why this works). The IServiceScopeFactory
has a CreateScope
method, which is used for creating new scope instances.
public class MySingletonService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public MySingletonService(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void Execute()
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var myScopedService = scope.ServiceProvider.GetService<IMyScopedService>();
myScopedService.DoSomething();
}
}
}
The created scope has it's own IServiceProvider
, which you can access to resolve your scoped services.
It is important to make sure that the scope only exists for as long as is necessary, and that it is properly disposed of once you have finished with it. This is to avoid any issues of captive dependencies (as discussed at the start of this article. Therefore, I would recommend:
- Only define the scope within the method that you intend to use it. It might be tempting to assign it to a field for reuse elsewhere in the singleton service, but again this will lead to captive dependencies.
- Wrap the scope in a
using
statement. This will ensure that the scope is properly disposed of once you have finished with it.
Conclusion
While trying to resolve scoped services within a singleton can often be a sign that your code needs refactoring, sometimes it is still necessary to do so.
Scoped services can be used in singletons by creating a scope and using the IServiceProvider
of the scope. However, it is important to make sure that the scope is cleaned up once it has been used to prevent captive dependencies.
If you found this article useful, please like it and share it. For more content like this, please follow this blog and follow me on Twitter. If you want, you can also buy me a coffee ! 😊
Top comments (0)