J'ai mis à jour un projet vers ASP.NET Core 2 aujourd'hui et j'obtiens le message d'erreur suivant:
Impossible de consommer le service défini IMongoDbContext de singleton IActiveUsersService
J'ai l'enregistrement suivant:
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)
{
...
}
}
Pourquoi DI ne peut pas consommer le service? Tout fonctionne bien pour ASP.NET Core 1.1.
Vous ne pouvez pas utiliser un service avec une durée de vie plus courte. Les services étendus n'existent que par requête, alors que les services singleton sont créés une fois et que l'instance est partagée.
Désormais, une seule instance de IActiveUsersService
existe dans l'application. Mais il veut dépendre de MongoDbContext
, qui est Scoped et créé à la demande.
Vous devrez soit:
MongoDbContext
un Singleton, ouIActiveUsersService
Scoped, ouMongoDbContext
au service utilisateur en tant qu'argument de fonctionIl existe des différences importantes entre les services Scoped et Singleton. L'avertissement est là pour mettre cela en lumière, et le désactiver ou changer de vie indistinctement pour le faire disparaître ne résoudra pas le problème.
Les services ciblés sont créés à partir d'une IServiceScope
. L'un de ses objectifs les plus importants est de s'assurer que tous les services IDisposable
qui sont créés dans cette étendue sont correctement éliminés lorsque l'étendue elle-même l'est.
Dans ASP.NET Core, une étendue de service est automatiquement créée pour chaque demande entrante. Vous n'avez donc généralement pas à vous en préoccuper. Cependant, vous pouvez également créer votre propre étendue de service. il vous suffit de vous en débarrasser.
Une façon de faire est de:
IDisposable
,IServiceProvider
,IServiceScope
à l'aide de la méthode IServiceProvider.CreateScope()
extension,Dispose
.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();
}
}
En fonction de votre utilisation de ces services et des services que vous utilisez, vous pouvez effectuer l'une des opérations suivantes:
IServiceProvider
(injectée), utilisez-la pour créer une nouvelle IServiceScope
à l'intérieur d'un bloc using
chaque fois que vous avez besoin d'un service limité, et laissez la portée être supprimée à la sortie du bloc.Gardez simplement à l'esprit que tous les services IDisposable
créés à partir d'une IServiceScope
seront automatiquement supprimés lorsque la portée elle-même le fera.
En bref, ne changez pas simplement la durée de vie de vos services pour "le faire fonctionner"; vous devez toujours y penser et vous assurer qu'ils sont éliminés correctement. ASP.NET Core gère automatiquement les cas les plus courants. pour d'autres, il vous suffit de faire un peu plus de travail.
Depuis la version 1.0, nous avons eu des blocs using()
pour nous assurer que les ressources sont éliminées correctement. Mais les blocs using()
ne fonctionnent pas quand quelque chose d'autre (le service DI) crée ces ressources pour vous. C'est là qu'interviennent les services Scoped, et leur utilisation incorrecte entraînerait des fuites de ressources dans votre programme.
Vous pouvez aussi ajouter
.UseDefaultServiceProvider(options =>
options.ValidateScopes = false)
before .Build()
dans le fichier Program.cs
pour désactiver la validation.
Essayez ceci uniquement pour les tests de développement, ActiveUsersService est singleton et a une durée de vie supérieure à celle de MongoDbContext qui est étendue et ne sera pas éliminé.
Il y a une autre façon d'aborder ce problème, et c'est en ajoutant MongoDbContext
à l'ID sous la forme AddTransient
comme ceci:
services.AddSingleton<IActiveUsersService, ActiveUsersService>();
services.AddTransient<IMongoDbContext, MongoDbContext>();
L'utilisation de cette approche signifie que vous obtiendrez une instance de MongoDbContext
pour chaque classe Singleton
que vous l'utilisez . Par exemple, si vous avez 10 classes Singleton utilisant MongoDbContext
, vous en aurez 10 instances: mais au lieu de créer une instance pour chaque requête.
Voir ceci pour référence: Impossible de consommer un service étendu de Singleton - Une leçon sur les étendues ASP DI Core DI