c# - tutorial - No se puede consumir el servicio con ámbito IMongoDbContext desde singleton IActiveUsersService después de actualizar a ASP.NET Core 2.0
asp.net core web api (4)
Hay otra forma de abordar este problema, y es mediante la adición de MongoDbContext
a la DI como AddTransient
esta manera:
services.AddSingleton<IActiveUsersService, ActiveUsersService>();
services.AddTransient<IMongoDbContext, MongoDbContext>();
El significado de usar este enfoque es que terminarás con una instancia de MongoDbContext
para cada clase de Singleton
estés usando. Por ejemplo, si tiene 10 clases Singleton usando MongoDbContext
, tendrá 10 instancias de eso, pero en lugar de crear una instancia para cada solicitud.
Vea esto como referencia: No se puede consumir el servicio de ámbito de Singleton - Una lección en ASP.net Core DI Scopes
Actualicé un proyecto a ASP.NET Core 2 hoy y recibo el siguiente error:
No se puede consumir el servicio con ámbito IMongoDbContext desde singleton IActiveUsersService
Tengo el siguiente registro:
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)
{
...
}
}
¿Por qué DI no puede consumir el servicio? Todo funciona bien para ASP.NET Core 1.1.
No se puede utilizar un servicio con una vida útil menor. Los servicios con ámbito solo existen por solicitud, mientras que los servicios de singleton se crean una vez y la instancia se comparte.
Ahora solo existe una instancia de IActiveUsersService
en la aplicación. Pero quiere depender de MongoDbContext
, que es Scoped, y se crea por solicitud.
Tendrá que:
- Hacer
MongoDbContext
un Singleton, o - Hacer
IActiveUsersService
Scoped, o - Pase
MongoDbContext
al servicio de usuario como un argumento de función
También puedes añadir
.UseDefaultServiceProvider(options =>
options.ValidateScopes = false)
antes de .Build()
en el archivo Program.cs
para deshabilitar la validación.
Intente esto solo para las pruebas de desarrollo, ActiveUsersService es único y tiene una vida útil más larga que MongoDbContext, que tiene un ámbito y no se eliminará.
Hay diferencias importantes entre los servicios de Scoped y Singleton. La advertencia está ahí para sacar esto a la luz, y apagarlo o cambiar las vidas de manera indiscriminada para que desaparezca no resolverá el problema.
Los servicios de ámbito se crean desde un IServiceScope
. Uno de sus propósitos más importantes es garantizar que todos los servicios IDisposable
que se crean en ese ámbito se eliminen adecuadamente cuando el alcance en sí mismo.
En ASP.NET Core, un alcance de servicio se crea automáticamente para usted en cada solicitud entrante, por lo que normalmente no necesita preocuparse por esto. Sin embargo, también puede crear su propio ámbito de servicio; solo necesitas deshacerte de ti mismo.
Una forma de hacer esto es:
-
IDisposable
su servicio de singletonIDisposable
, - inyectar
IServiceProvider
, - cree y almacene un ámbito
IServiceScope
utilizando el método de extensiónIServiceProvider.CreateScope()
, - use ese ámbito para crear el servicio de ámbito que necesita,
-
Dispose
el alcance del servicio en el métodoDispose
.
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();
}
}
Dependiendo de cómo utilice estos y los servicios de alcance que consume, podría hacer uno de los siguientes:
- cree una única instancia del servicio con ámbito y úsela durante la vida útil del singleton; o
- almacene una referencia al
IServiceProvider
raíz (inyectado), utilícelo para crear un nuevoIServiceScope
dentro de un bloque deusing
cada vez que necesite un servicio de ámbito, y deje que el alcance se elimine cuando el bloque salga.
Solo tenga en cuenta que cualquier servicio IDisposable
creado a partir de un IServiceScope
se eliminará automáticamente cuando el alcance sí lo haga.
En resumen, no se limite a cambiar la duración de sus servicios para "hacer que funcione"; Todavía debe pensar en ellos y asegurarse de que se eliminen adecuadamente. ASP.NET Core maneja los casos más comunes automáticamente; Para otros, solo necesitas trabajar un poco más.
Desde C # 1.0 hemos tenido que using()
bloques para garantizar que los recursos se eliminen correctamente. Pero el using()
bloques using()
no funciona cuando otra cosa (el servicio DI) está creando esos recursos para usted. Ahí es donde entran los servicios de Scoped, y su uso incorrecto conducirá a fugas de recursos en su programa.