I updated a project to ASP.NET Core 2 today and I get the following error:
Cannot consume scoped service IMongoDbContext from singleton IActiveUsersService
I have the following registration:
services.AddSingleton<IActiveUsersService, ActiveUsersService>();
services.AddScoped<IMongoDbContext, MongoDbContext>();
services.AddSingleton(option =>
{
var client = new MongoClient(MongoConnectionString.Settings);
return client.GetDatabase(MongoConnectionString.Database);
})
public class MongoDbContext : IMongoDbContext
{
private readonly IMongoDatabase _database;
public MongoDbContext(IMongoDatabase database)
{
_database = database;
}
public IMongoCollection<T> GetCollection<T>() where T : Entity, new()
{
return _database.GetCollection<T>(new T().CollectionName);
}
}
public class IActiveUsersService: ActiveUsersService
{
public IActiveUsersService(IMongoDbContext mongoDbContext)
{
...
}
}
Why DI can't consume the service? All works fine for ASP.NET Core 1.1.
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).
AddTransient() With a transient service, a new instance is provided every time a service instance is requested whether it is in the scope of the same HTTP request or across different HTTP requests. Follow this answer to receive notifications.
You should almost never consume scoped service or transient service from a singleton. You should also avoid consuming transient service from a scoped service.
First, DbContext is a lightweight object; it is designed to be used once per business transaction. Making your DbContext a Singleton and reusing it throughout the application can cause other problems, like concurrency and memory leak issues. And the DbContext class is not thread safe.
You can't use a service with a smaller lifetime. Scoped services only exist per-request, while singleton services are created once and the instance is shared.
Now only one instance of IActiveUsersService
exists in the app. But it wants to depend on MongoDbContext
, which is Scoped, and is created per-request.
You will have to either:
MongoDbContext
a Singleton, orIActiveUsersService
Scoped, orMongoDbContext
into the user service as a function argumentThere are important differences between Scoped and Singleton services. The warning is there to bring this to light, and turning it off or switching around lifetimes indiscriminately to make it go away won't solve the problem.
Scoped services are created from an IServiceScope
. One of its most important purposes is to ensure that any IDisposable
services which are created in that scope are properly disposed when the scope itself is.
In ASP.NET Core, a service scope is automatically created for you on each incoming request, so you ordinarily don't need to worry about this. However, you can also create your own service scope; you just need to dispose of it yourself.
One way to do this is to:
IDisposable
,IServiceProvider
,IServiceScope
scope using the IServiceProvider.CreateScope()
extension method,Dispose
method.services.AddSingleton<IActiveUsersService, ActiveUsersService>();
services.AddScoped<IMongoDbContext, MongoDbContext>();
services.AddSingleton(option =>
{
var client = new MongoClient(MongoConnectionString.Settings);
return client.GetDatabase(MongoConnectionString.Database);
})
public class MongoDbContext : IMongoDbContext
{
private readonly IMongoDatabase _database;
public MongoDbContext(IMongoDatabase database)
{
_database = database;
}
public IMongoCollection<T> GetCollection<T>() where T : Entity, new()
{
return _database.GetCollection<T>(new T().CollectionName);
}
}
public class ActiveUsersService: IActiveUsersService, IDisposable
{
private readonly IServiceScope _scope;
public ActiveUsersService(IServiceProvider services)
{
_scope = services.CreateScope(); // CreateScope is in Microsoft.Extensions.DependencyInjection
}
public IEnumerable<Foo> GetFooData()
{
using (var context = _scope.ServiceProvider.GetRequiredService<IMongoDbContext>())
{
return context.GetCollection<Foo>();
}
}
public void Dispose()
{
_scope?.Dispose();
}
}
Depending on how you're using these and the scoped services you're consuming, you could instead do one of the following:
IServiceProvider
, use it to create a new IServiceScope
inside a using
block every time you need a scoped service, and let the scope get disposed when the block exits.Just keep in mind that any IDisposable
services created from an IServiceScope
will get automatically disposed when the scope itself does.
In short, don't just change around the lifetimes of your services to "make it work"; you still need to think about those and be sure they get disposed properly. ASP.NET Core handles the most common cases automatically; for others, you just need to do a bit more work.
Ever since C# 1.0 we have had using()
blocks to ensure resources are disposed correctly. But using()
blocks don't work when something else (the DI service) is creating those resources for you. That's where Scoped services come in, and using them incorrectly will lead to resource leaks in your program.
You can also add
.UseDefaultServiceProvider(options =>
options.ValidateScopes = false)
before .Build()
in Program.cs
file to disable the validation.
Try this only for development testing, ActiveUsersService is singleton and has a larger lifetime than MongoDbContext which is scoped and will not get disposed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With