web-dev-qa-db-fra.com

Impossible de consommer le service défini IMongoDbContext de singleton IActiveUsersService après la mise à niveau vers ASP.NET Core 2.0

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.

26
user348173

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:

  1. Faites de MongoDbContext un Singleton, ou
  2. Faites IActiveUsersService Scoped, ou
  3. Passez MongoDbContext au service utilisateur en tant qu'argument de fonction
36
juunas

Il 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:

  • faire votre service singleton IDisposable,
  • injecter IServiceProvider,
  • créer et stocker une étendue IServiceScope à l'aide de la méthode IServiceProvider.CreateScope() extension,
  • utilisez cette étendue pour créer le service dont vous avez besoin,
  • supprime l'étendue du service dans la méthode 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:

  • créer une seule instance du service limité et l'utiliser pour la vie du singleton; ou
  • stocke une référence à la racine 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.

8
Toby J

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é.

2
Robin Thomas

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

1
Liran Friedman