512 votes

Comment enregistrer plusieurs implémentations de la même interface dans Asp.Net Core?

J'ai des services qui sont dérivées à partir de la même interface

public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { } 
public class ServiceC : IService { }

Généralement d'autres conteneurs IOC comme Unity vous permettent d'enregistrer des implémentations concrètes par certains Key qui les distingue.

Dans Asp.Net Core comment puis-je m'inscrire à ces services et à résoudre lors de l'exécution basé sur certains points?

Je ne vois pas du tout de l' Add méthode de Service prend key ou name paramètre généralement utilisé pour distinguer la mise en œuvre concrète.

    public void ConfigureServices(IServiceCollection services)
    {            
         // How do I register services here of the same interface            
    }


    public MyController:Controller
    {
       public void DoSomeThing(string key)
       { 
          // How do get service based on key
       }
    }

Est le modèle de Fabrique de la seule option ici?

Update1
J'ai traversé l'article ici qui montre comment utiliser l'usine modèle pour obtenir des instances de service lorsque nous avons plusieurs concreate mise en œuvre. Cependant, il n'est pas encore complet. quand je l'appelle _serviceProvider.GetService() méthode que je ne peut pas injecter des données dans le constructeur. Par exemple, considérons cet exemple

public class ServiceA : IService
{
     private string _efConnectionString;
     ServiceA(string efconnectionString)
     {
       _efConnecttionString = efConnectionString;
     } 
}

public class ServiceB : IService
{    
   private string _mongoConnectionString;
   public ServiceB(string mongoConnectionString)
   {
      _mongoConnectionString = mongoConnectionString;
   }
}

public class ServiceC : IService
{    
    private string _someOtherConnectionString
    public ServiceC(string someOtherConnectionString)
    {
      _someOtherConnectionString = someOtherConnectionString;
    }
}

Comment peut - _serviceProvider.GetService() injecter de la chaîne de connexion appropriée? Dans l'Unité ou de tout autre CIO, nous pouvons le faire au moment de l'inscription. Je peux utiliser IOption cependant qui va m'obliger à injecter tous les paramètres, je ne peut pas injecter un particulier connectionstring dans le service.

Notez également que je suis en train d'essayer d'éviter d'utiliser d'autres conteneurs (y compris l'Unité) car puis-je m'inscrire tout le reste ( par exemple, les Contrôleurs) avec un nouveau conteneur.

Aussi à l'aide de modèle de fabrique pour créer une instance de service est contre les TREMPER dans l'usine augmente le nombre de dépendances le client est obligé de dépendre de détails ici

Je pense donc que le défaut DI ASP.NET core manque 2 choses
1>Enregistrer instances à l'aide de la clé
2>Injecter des données statiques dans le constructeur lors de l'inscription

427voto

Miguel A. Arilla Points 1173

J'ai une solution simple à l'aide de Func quand je me suis retrouvé dans cette situation.

services.AddTransient<Consumer>();

services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
services.AddTransient<ServiceC>();

services.AddTransient<Func<string, IService>>(serviceProvider => key =>
{
    switch(key)
    {
        case "A":
            return serviceProvider.GetService<ServiceA>();
        case "B":
            return serviceProvider.GetService<ServiceB>();
        case "C":
            return serviceProvider.GetService<ServiceC>();
        default:
            throw new KeyNotFoundException(); // or maybe return null, up to you
    }
});

Et de l'utiliser de toute la classe enregistrée avec le DI comme:

public class Consumer
{
    private readonly Func<string, IService> _serviceAccessor;

    public Consumer(Func<string, IService> serviceAccessor)
    {
        _serviceAccessor = serviceAccesor;
    }

    public void UseServiceA()
    {
        //use _serviceAccessor field to resolve desired type
        _serviceAccessor("A").DoIServiceOperation();
    }
}

Mise à JOUR

Gardez à l'esprit que, dans cet exemple, la clé de la résolution est une chaîne de caractères, par souci de simplicité, et parce que l'OP a été de demander, pour ce cas particulier.

Mais vous pouvez utiliser n'importe quel type de résolution sous forme de clé, que vous ne voulez généralement un énorme n-switch pourrir votre code. Dépend de la façon dont votre application échelles.

153voto

rnrneverdies Points 1220

Une autre option est d'utiliser la méthode d'extension GetServices de Microsoft.Extensions.DependencyInjection.

Inscrire vos services en tant que:

services.AddSingleton<IService, ServiceA>();
services.AddSingleton<IService, ServiceB>();
services.AddSingleton<IService, ServiceC>();

Ensuite résoudre avec un peu de Linq:

var services = serviceProvider.GetServices<IService>();
var serviceB = services.First(o => o.GetType() == typeof(ServiceB));

ou

var serviceZ = services.First(o => o.Name.Equals("Z"));

(en supposant que l' IService a une propriété de chaîne appelée "Nom")

Assurez-vous que using Microsoft.Extensions.DependencyInjection;

Mise à jour

AspNet 2,1 source: GetServices

21voto

Gerardo Grignoli Points 782

Il n'est pas pris en charge par Microsoft.Extensions.DependencyInjection.

Mais vous pouvez le plug-in d'un autre mécanisme d'injection de dépendance, comme StructureMap Voir la page d'Accueil et c'est GitHub du Projet.

Il n'est pas difficile du tout:

  1. Ajouter une dépendance à StructureMap dans votre project.json:

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
    
  2. Et l'injecter dans la ASP.NET pipeline à l'intérieur d' ConfigureServices et inscrivez vos classes (voir docs)

    public IServiceProvider ConfigureServices(IServiceCollection services) // returns IServiceProvider !
    {
        // Add framework services.
        services.AddMvc();
        services.AddWhatever();
    
        //using StructureMap;
        var container = new Container();
        container.Configure(config =>
        {
            // Register stuff in container, using the StructureMap APIs...
            config.For<IPet>().Add(new Cat("CatA")).Named("A");
            config.For<IPet>().Add(new Cat("CatB")).Named("B");
            config.For<IPet>().Use("A"); // Optionally set a default
            config.Populate(services);
        });
    
        return container.GetInstance<IServiceProvider>();
    }
    
  3. Ensuite, pour obtenir une instance nommée, vous devez demander à l' IContainer

    public class HomeController : Controller
    {
        public HomeController(IContainer injectedContainer)
        {
            var myPet = injectedContainer.GetInstance<IPet>("B");
            string name = myPet.Name; // Returns "CatB"
    

C'est tout.

Pour l'exemple de construire, vous avez besoin

    public interface IPet
    {
        string Name { get; set; }
    }

    public class Cat : IPet
    {
        public Cat(string name)
        {
            Name = name;
        }

        public string Name {get; set; }
    }

15voto

Sock Points 3300

Vous avez raison, est construit dans le ASP.NET de Base conteneur n'a pas le concept de l'inscription de plusieurs des services et de la récupération d'un spécifique, comme vous le suggérez, une usine est la seule solution dans ce cas.

Sinon, vous pouvez passer à un tiers récipient comme l'Unité ou StructureMap qui fournit la solution dont vous avez besoin (documentée ici: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing-the-default-services-container).

12voto

neleus Points 91

J'ai été confronté au même problème et que vous souhaitez partager comment je l'ai résolu, et pourquoi.

Comme vous l'avez mentionné, il y a deux problèmes. La première:

Dans Asp.Net Core comment puis-je m'inscrire à ces services et de les résoudre à d'exécution basé sur certains points?

Alors, quelles options avons-nous? Les gens suggèrent deux:

  • Utiliser une personnalisée en usine (comme _myFactory.GetServiceByKey(key))

  • Utiliser une autre DI moteur (comme _unityContainer.Resolve<IService>(key))

Est le modèle de Fabrique de la seule option ici?

En fait, ces deux options sont les usines parce que chaque Conteneur IoC est aussi une usine (hautement configurable et compliqué). Et il me semble que d'autres options sont également les variations de l'Usine modèle.

Alors, quelle est la meilleure option? Ici, je suis d'accord avec @Chaussette qui a suggéré à l'aide personnalisée en usine, et c'est pourquoi.

Tout d'abord, j'essaie toujours d'éviter d'ajouter de nouvelles dépendances lorsqu'ils ne sont pas vraiment nécessaires. Je suis d'accord avec vous sur ce point. En outre, à l'aide de deux DI cadres est pire que de la création de l'usine de l'abstraction. Dans le deuxième cas, vous devez ajouter de nouvelles dépendances du paquet (comme l'Unité), mais selon une nouvelle usine de l'interface est moins mal ici. L'idée principale de ASP.NET Core DI, je crois, est la simplicité. Il maintient un ensemble minimal de fonctionnalités suivantes principe de BAISER. Si vous avez besoin d'une fonctionnalité supplémentaire, puis de BRICOLAGE ou de l'utilisation d'un correspondant Plungin qui implémente la fonction souhaitée (Principe Ouvert Fermé).

Deuxièmement, nous avons souvent besoin d'injecter plusieurs noms de dépendances de service unique. Dans le cas de l'Unité, vous pourriez avoir à spécifier des noms pour les paramètres du constructeur (à l'aide d' InjectionConstructor). Cet enregistrement utilise la réflexion et intelligente logique de deviner les arguments du constructeur. Cela peut également conduire à des erreurs d'exécution, si l'inscription ne correspondent pas aux arguments du constructeur. D'autre part, lors de l'utilisation de votre propre usine, vous avez le plein contrôle de la façon de fournir les paramètres du constructeur. C'est plus lisible et qu'il est résolu à la compilation. Principe de BAISER à nouveau.

Le deuxième problème:

Comment peut-_serviceProvider.La méthode GetService() injecter de connexion approprié chaîne?

Tout d'abord, je suis d'accord avec vous que, selon de nouvelles choses, comme IOptions (et donc sur l'emballage) Microsoft.Extensions.Options.ConfigurationExtensions) n'est pas une bonne idée. J'ai vu certains de discuter de ce sujet IOptions où il y avait des opinions différentes à propos de sa prestation en nature. Encore une fois, j'essaie d'éviter d'ajouter de nouvelles dépendances lorsqu'ils ne sont pas vraiment nécessaires. Est-il vraiment nécessaire? Je crois que non. Sinon, chaque mise en œuvre devrait dépendre sans besoin de l'arrivée de cette mise en œuvre (pour moi, il ressemble à la violation de l'ISP, où je suis d'accord avec vous aussi). Cela est également vrai de la fonction de l'usine, mais dans ce cas, il peut être évitée.

L'ASP.NET Core DI offre un très beau surcharge pour:

var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
             s => new MyFactoryImpl(
                 mongoConnection, efConnection, otherConnection, 
                 s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X