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

240

J'ai des services dérivés de la même interface.

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

En règle générale, d'autres conteneurs IoC comme Unityvous permettent d'enregistrer des implémentations concrètes par certains Keyqui les distinguent.

Dans ASP.NET Core, comment enregistrer ces services et les résoudre au moment de l'exécution en fonction d'une clé?

Je ne vois aucune Addméthode de service qui prend un paramètre keyou name, qui serait généralement utilisé pour distinguer l'implémentation concrète.

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


    public MyController:Controller
    {
       public void DoSomething(string key)
       { 
          // How do I resolve the service by key?
       }
    }

Le modèle d'usine est-il la seule option ici?

Update1
J'ai parcouru l'article ici qui montre comment utiliser le modèle d'usine pour obtenir des instances de service lorsque nous avons plusieurs implémentations concrètes. Cependant, ce n'est pas encore une solution complète. Quand j'appelle le_serviceProvider.GetService() méthode, je ne peux pas injecter de données dans le constructeur.

Par exemple, considérez ceci:

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-on _serviceProvider.GetService()injecter la chaîne de connexion appropriée? Dans Unity ou dans toute autre bibliothèque IoC, nous pouvons le faire lors de l'enregistrement du type. Je peux utiliser IOption , cependant, cela me demandera d'injecter tous les paramètres. Je ne peux pas injecter une chaîne de connexion particulière dans le service.

Notez également que j'essaie d'éviter d'utiliser d'autres conteneurs (y compris Unity) car je dois alors enregistrer tout le reste (par exemple, les contrôleurs) avec le nouveau conteneur également.

En outre, l'utilisation du modèle d'usine pour créer des instances de service est contraire à DIP, car elle augmente le nombre de dépendances qu'un client a des détails ici .

Donc, je pense que la DI par défaut dans ASP.NET Core manque deux choses:

  1. La possibilité d'enregistrer des instances à l'aide d'une clé
  2. La possibilité d'injecter des données statiques dans les constructeurs lors de l'enregistrement
LP13
la source
4
Copie
2
Il existe enfin une extension dans nuget pour les enregistrements basés sur le nom, j'espère que cela peut aider
neleus
Salut, désolé pour ma question stupide, mais je suis à peu près nouveau avec Microsoft.Extensions.DependencyInjection ... pensez-vous que créer 3 interfaces vides qui étendent Iservice comme "interface publique IServiceA: IService" et que "classe publique ServiceA: IServiceA "... pourrait être une bonne pratique?
Emiliano Magliocca
cet article est-il utile? stevejgordon.co.uk/…
Mike B
Peut Update1être déplacé vers une question différente car l'injection de choses dans les constructeurs est très différente de l'élaboration de l'objet à construire
Neil

Réponses:

246

J'ai fait une solution de contournement simple en utilisant Funcquand je me suis retrouvé dans cette situation.

Déclarez d'abord un délégué partagé:

public delegate IService ServiceResolver(string key);

Ensuite, dans votre Startup.cs, configurez les multiples enregistrements concrets et une cartographie manuelle de ces types:

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

services.AddTransient<ServiceResolver>(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 utilisez-le dans n'importe quelle classe enregistrée auprès de DI:

public class Consumer
{
    private readonly IService _aService;

    public Consumer(ServiceResolver serviceAccessor)
    {
        _aService = serviceAccessor("A");
    }

    public void UseServiceA()
    {
        _aService.DoTheThing();
    }
}

Gardez à l'esprit que dans cet exemple, la clé de la résolution est une chaîne, par souci de simplicité et parce que OP demandait ce cas en particulier.

Mais vous pouvez utiliser n'importe quel type de résolution personnalisé comme clé, car vous ne voulez généralement pas un énorme commutateur n-case pourrir votre code. Cela dépend de la façon dont votre application évolue.

Miguel A. Arilla
la source
1
@MatthewStevenMonkan a mis à jour ma réponse avec un exemple
Miguel A. Arilla
2
Utiliser un modèle d'usine comme celui-ci est la meilleure façon de procéder. Merci d'avoir partagé!
Sergey Akopov
2
+1 Très soigné et propre, car lorsque nous utilisons un autre di-conteneur, nous devons inclure leur package chaque fois que nous devons résoudre des dépendances, par exemple. ILifetimeScope dans AutoFac.
Anupam Singh
1
@AnupamSingh À mon avis, la plupart des petites et moyennes applications fonctionnant sur .NET Core n'ont pas besoin de cadre de DI, ajoute simplement de la complexité et des dépendances indésirables, la beauté et la simplicité du DI intégré sont plus que suffisantes, et elles peuvent également être étendu avec facilité.
Miguel A. Arilla
9
Explication du vote à la baisse - C'est très intéressant mais je suis en train de refactoriser une base de code massive pour supprimer toute cette magie Func que quelqu'un a faite il y a quelques années (avant la révolution MS DI) Le problème avec cela est qu'il augmente considérablement la complexité de la connascence sur les propriétés qui peut entraîner une résolution DI compliquée plus loin sur la ligne. Par exemple, j'ai travaillé sur un gestionnaire de services Windows qui avait plus de 1,6 k lignes de code à faire avec Func et après l'avoir fait, la méthode recommandée de DI, je l'ai réduite à 0,2 k lignes. Les lignes de code OK ne signifient rien .. sauf qu'il est plus facile à lire et à réutiliser maintenant ...
Piotr Kula
79

Une autre option consiste à utiliser la méthode d'extension GetServicesdeMicrosoft.Extensions.DependencyInjection .

Enregistrez vos services en tant que:

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

Puis résolvez 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 admettant que IService possède une propriété de chaîne appelée "Nom")

Assurez-vous d'avoir using Microsoft.Extensions.DependencyInjection;

Mettre à jour

Source AspNet 2.1: GetServices

rnrneverdies
la source
6
Pas sûr, mais je pense que ce n'est pas déterministe. Les résultats que vous obtenez aujourd'hui peuvent changer demain, cela ne semble pas être une bonne pratique.
rnrneverdies
4
upvote au lien pour GetServices, qui m'a montré que vous pouvez demander une liste de services un service dépendant en demandantIEnumerable<IService>
johnny 5
20
serviceProvider.GetServices <IService> () instanciera chacun de ServiceA, ServiceB et ServiceC. Vous souhaitez appeler le constructeur d'un seul service - celui dont vous avez réellement besoin. C'est un gros problème si les implémentations ne sont pas légères ou si vous avez plusieurs implémentations de IService (par exemple, vous avez des implémentations générées automatiquement d'IRepository pour chaque modèle).
Uros
6
Je suis d'accord avec @Uros. Ce n'est pas une bonne solution. Imaginez ce qui se passe si vous enregistrez 10 implémentations IService et que l'instance dont vous avez réellement besoin est la dernière. Dans ce cas, 9 instances sont réellement créées par DI, qui ne sont jamais utilisées.
thomai
4
Mauvaise idée: plusieurs instances inutilisées, anti modèle de localisateur de service et couplage direct à l'implémentation réelle (type de <ServiceA>).
Rico Suter
20

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

Mais vous pouvez brancher un autre mécanisme d'injection de dépendance, comme StructureMap voir sa page d'accueil et son projet GitHub .

Ce n'est pas difficile du tout:

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

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
  2. Injectez-le dans le pipeline ASP.NET à l'intérieur ConfigureServiceset enregistrez vos classes (voir la documentation)

    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 devrez demander le 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 à 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; }
    }
Gerardo Grignoli
la source
J'ai essayé cette approche, mais j'obtiens des erreurs d'exécution sur mon contrôleur car IContainer n'est pas trouvé dans les plans de build. Y a-t-il quelque chose que je dois faire pour exiger qu'IContainer soit auto-injecté?
mohrtan
BTW, j'utilise StructureMap.Micorosoft.DependencyInjection 1.3.0.
mohrtan
Renvoyez-vous le nouveau conteneur dans ConfigureServices?
Gerardo Grignoli
Je renvoie l'IServiceProviderInstance du nouveau conteneur comme indiqué à l'étape 2 ci-dessus. Je l'ai copié exactement en le modifiant uniquement pour mes types. Ceci est une bonne solution et fonctionne parfaitement. Le seul inconvénient est que je ne peux pas utiliser de conteneur injecté et que j'ai recours à un conteneur statique, ce que je ne veux pas faire.
mohrtan
1
Cela fonctionne pour moi grâce à GerardoGrignoli. @mohrtan, l'exemple de code est ici si vous êtes toujours à la recherche. github.com/Yawarmurtaza/AspNetCoreStructureMap
Yawar Murtaza
13

Vous avez raison, le conteneur ASP.NET Core intégré n'a pas le concept d'enregistrer plusieurs services et d'en récupérer un spécifique, comme vous le suggérez, une usine est la seule vraie solution dans ce cas.

Alternativement, vous pouvez passer à un conteneur tiers comme Unity ou StructureMap qui fournit la solution dont vous avez besoin (documentée ici: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- le conteneur de services par défaut ).

Chaussette
la source
13

J'ai rencontré le même problème et je veux 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 enregistrer ces services et les résoudre lors de l'exécution en fonction d'une clé?

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

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

  • Utilisez un autre moteur DI (comme _unityContainer.Resolve<IService>(key))

Le modèle d'usine est-il la seule option ici?

En fait, les deux options sont des usines car chaque conteneur IoC est également une usine (très configurable et compliqué cependant). Et il me semble que d'autres options sont également des variations du modèle Factory.

Alors quelle option est la meilleure alors? Ici, je suis d'accord avec @Sock qui a suggéré d'utiliser une usine personnalisée, et c'est pourquoi.

Tout d'abord, j'essaie toujours d'éviter d'ajouter de nouvelles dépendances quand elles ne sont pas vraiment nécessaires. Je suis donc d'accord avec vous sur ce point. De plus, l'utilisation de deux infrastructures DI est pire que la création d'une abstraction d'usine personnalisée. Dans le second cas, vous devez ajouter une nouvelle dépendance de package (comme Unity), mais dépendre d'une nouvelle interface d'usine est moins mal ici. L'idée principale d'ASP.NET Core DI, je crois, est la simplicité. Il maintient un ensemble minimal de fonctionnalités suivant le principe KISS . Si vous avez besoin de fonctionnalités supplémentaires, bricolage ou utilisez un plongeur correspondant qui implémente la fonctionnalité souhaitée (principe fermé ouvert).

Deuxièmement, nous devons souvent injecter de nombreuses dépendances nommées pour un service unique. Dans le cas d'Unity, vous devrez peut-être spécifier des noms pour les paramètres du constructeur (en utilisant InjectionConstructor). Cet enregistrement utilise la réflexion et une logique intelligente pour deviner les arguments du constructeur. Cela peut également entraîner des erreurs d'exécution si l'enregistrement ne correspond pas aux arguments du constructeur. D'un autre côté, lorsque vous utilisez votre propre usine, vous avez un contrôle total sur la façon de fournir les paramètres du constructeur. Il est plus lisible et résolu au moment de la compilation. Principe KISS nouveau.

Le deuxième problème:

Comment _serviceProvider.GetService () peut-il injecter la chaîne de connexion appropriée?

Tout d'abord, je suis d'accord avec vous que dépendre de nouvelles choses comme IOptions(et donc du package Microsoft.Extensions.Options.ConfigurationExtensions) n'est pas une bonne idée. J'ai vu des discussions sur les IOptionsdifférents points de vue sur ses avantages. Encore une fois, j'essaie d'éviter d'ajouter de nouvelles dépendances quand elles ne sont pas vraiment nécessaires. Est-ce vraiment nécessaire? Je pense que non. Sinon, chaque implémentation devrait en dépendre sans aucun besoin clair provenant de cette implémentation (pour moi, cela ressemble à une violation du FAI, où je suis également d'accord avec vous). Cela est également vrai en fonction de l'usine, mais dans ce cas, cela peut être évité.

L'ASP.NET Core DI fournit une très belle surcharge à cet effet:

var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
             s => new MyFactoryImpl(
                 mongoConnection, efConnection, otherConnection, 
                 s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));
neleus
la source
Salut, désolé pour ma question stupide, mais je suis nouveau avec Microsoft.Extensions.DependencyInjection ... pensez-vous que créer 3 interfaces qui étendent Iservice comme "interface publique IServiceA: IService" et que "classe publique ServiceA: IServiceA" ... pourrait être une bonne option de pratique?
Emiliano Magliocca
1
@ emiliano-magliocca En général, vous ne devez pas dépendre des interfaces que vous n'utilisez pas (ISP), IServiceAdans votre cas. Puisque vous n'utilisez que des méthodes de IService, vous ne devez avoir de dépendance IServiceque pour .
neleus
1
@ cagatay-kalan En cas de question d'OP, il peut facilement atteindre son objectif avec ASP.NET Core DI. Pas besoin d'autres frameworks DI.
neleus
1
@EmilianoMagliocca Cela peut être facilement résolu de cette façon: services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));pour la première classe et services.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));pour la seconde.
neleus
1
@EmilianoMagliocca dans mon exemple, 'MyFirstClass' et 'MySecondClass' ont le même paramètre ctor de type d'interface que Escpos et Usbpos implémentent. Ainsi, le code ci-dessus indique uniquement au conteneur IoC comment instancier «MyFirstClass» et «MySecondClass». Rien de plus. Donc, en plus, vous devrez peut-être mapper une ou plusieurs autres interfaces avec «MyFirstClass» et «MySecondClass». Cela dépend de vos besoins et je ne l'ai pas couvert dans mon exemple.
neleus
13

J'injecte simplement un IEnumerable

ConfigureServices dans Startup.cs

Assembly.GetEntryAssembly().GetTypesAssignableFrom<IService>().ForEach((t)=>
                {
                    services.AddScoped(typeof(IService), t);
                });

Dossier Services

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

public class ServiceA : IService
{
    public string Name { get { return "A"; } }
}

public class ServiceB : IService
{    
    public string Name { get { return "B"; } }
}

public class ServiceC : IService
{    
    public string Name { get { return "C"; } }
}

MyController.cs

public class MyController
{
    private readonly IEnumerable<IService> _services;
    public MyController(IEnumerable<IService> services)
    {
        _services = services;
    }
    public void DoSomething()
    {
        var service = _services.Where(s => s.Name == "A").Single();
    }
...
}

Extensions.cs

    public static List<Type> GetTypesAssignableFrom<T>(this Assembly assembly)
    {
        return assembly.GetTypesAssignableFrom(typeof(T));
    }
    public static List<Type> GetTypesAssignableFrom(this Assembly assembly, Type compareType)
    {
        List<Type> ret = new List<Type>();
        foreach (var type in assembly.DefinedTypes)
        {
            if (compareType.IsAssignableFrom(type) && compareType != type)
            {
                ret.Add(type);
            }
        }
        return ret;
    }
T Brown
la source
Dans la méthode DoSomething () du Controller, vous pouvez utiliser typeof pour résoudre le service souhaité: var service = _services.FirstOrDefault (t => t.GetType () == typeof (ServiceA));
Ciaran Bruen
J'ai littéralement tout essayé, et c'est la seule solution qui a fonctionné pour moi. Merci!
Skatz1990
@ Skatz1990 Essayez la solution que j'ai créée ci-dessous dans un autre article. Je pense que c'est plus propre et plus simple à utiliser.
T Brown
12

La plupart des réponses ici violent le principe de responsabilité unique (une classe de service ne doit pas résoudre les dépendances elle-même) et / ou utilisent l'anti-modèle de localisateur de service.

Une autre option pour éviter ces problèmes consiste à:

  • utiliser un paramètre de type générique supplémentaire sur l'interface ou une nouvelle interface implémentant l'interface non générique,
  • implémenter une classe adaptateur / intercepteur pour ajouter le type de marqueur, puis
  • utiliser le type générique comme «nom»

J'ai écrit un article avec plus de détails: Injection de dépendances dans .NET: un moyen de contourner les enregistrements nommés manquants

Rico Suter
la source
comment la réponse acceptée viole-t-elle le principe de la responsabilité unique?
LP13
Voir les commentaires de stackoverflow.com/a/52066039/876814 et également dans la réponse acceptée, le service est résolu paresseusement, c'est-à-dire que vous savez seulement s'il échoue au moment de l'exécution et qu'il n'y a aucun moyen de vérifier statiquement cela au démarrage après la construction du conteneur (similaire à la réponse dans le commentaire). SRP car le service n'est pas seulement responsable de sa logique métier mais aussi de la résolution des dépendances
Rico Suter
@RicoSuter J'aime vraiment la solution dans votre blog, mais je suis confus par votre DI au sein de la classe de démarrage. Plus précisément, je ne comprends pas la ligne MessagePublisher ("MyOrderCreatedQueue") car je ne vois pas de constructeur avec cette signature. services.AddSingleton <IMessagePublisher <OrderCreatedMessage>> (nouveau MessagePublisher <OrderCreatedMessage> (nouveau MessagePublisher ("MyOrderCreatedQueue")));
Lee Z
Merci, mis à jour l'article et utilisez MyMessagePublisher comme exemple d'implémentation d'IMessagePublisher
Rico Suter
7

Un peu tard pour cette soirée, mais voici ma solution: ...

Startup.cs ou Program.cs si gestionnaire générique ...

services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>();
services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();

IMyInterface de configuration de l'interface T

public interface IMyInterface<T> where T : class, IMyInterface<T>
{
    Task Consume();
}

Implémentations concrètes de IMyInterface de T

public class CustomerSavedConsumer: IMyInterface<CustomerSavedConsumer>
{
    public async Task Consume();
}

public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer>
{
    public async Task Consume();
}

J'espère que s'il y a un problème à le faire de cette façon, quelqu'un voudra bien montrer pourquoi ce n'est pas la bonne façon de procéder.

gris
la source
3
IMyInterface<CustomerSavedConsumer>et IMyInterface<ManagerSavedConsumer>sont des types de services différents - cela ne répond pas du tout à la question des PO.
Richard Hauer
2
L'OP voulait un moyen d'enregistrer plusieurs implémentations de la même interface dans le noyau Asp.net. Si je ne l'ai pas fait, veuillez expliquer comment (exactement).
Gray
1
Tant que vous avez raison, ce motif permet l'effet souhaité par l'op. Au moins, lorsque j'essayais de le faire moi-même, je suis tombé sur ce message et ma solution a fonctionné le mieux pour ma situation.
Gris
1
Je pense que le problème était plus que l'enregistrement de plusieurs implémentations pour une seule interface (à l'aide de MS DI) ne permet pas au conteneur de distinguer une implémentation d'une autre. Dans d'autres DI, vous pouvez les saisir pour que le conteneur sache lequel choisir. Dans MS, vous devez utiliser un délégué et choisir manuellement. Votre solution ne résout pas ce scénario car vos interfaces sont différentes, le conteneur n'a donc aucun problème à choisir la bonne implémentation. Bien que votre échantillon fonctionne évidemment, ce n'est pas une solution au problème comme indiqué.
Richard Hauer
3
@Gray Même si votre message a reçu une mauvaise presse, je vous remercie d'avoir proposé cette solution. Il offre aux lecteurs une autre option pour surmonter les limitations des cœurs .net DI. Bien qu'il ne puisse pas répondre directement à la question des PO, il fournit une solution alternative parfaite, c'est quoi le SO, non?
Neil Watson
5

Apparemment, vous pouvez simplement injecter IEnumerable de votre interface de service! Trouvez ensuite l'instance que vous souhaitez utiliser avec LINQ.

Mon exemple est pour le service AWS SNS mais vous pouvez vraiment faire la même chose pour n'importe quel service injecté.

Commencez

foreach (string snsRegion in Configuration["SNSRegions"].Split(',', StringSplitOptions.RemoveEmptyEntries))
{
    services.AddAWSService<IAmazonSimpleNotificationService>(
        string.IsNullOrEmpty(snsRegion) ? null :
        new AWSOptions()
        {
            Region = RegionEndpoint.GetBySystemName(snsRegion)
        }
    );
}

services.AddSingleton<ISNSFactory, SNSFactory>();

services.Configure<SNSConfig>(Configuration);

SNSConfig

public class SNSConfig
{
    public string SNSDefaultRegion { get; set; }
    public string SNSSMSRegion { get; set; }
}

appsettings.json

  "SNSRegions": "ap-south-1,us-west-2",
  "SNSDefaultRegion": "ap-south-1",
  "SNSSMSRegion": "us-west-2",

Usine SNS

public class SNSFactory : ISNSFactory
{
    private readonly SNSConfig _snsConfig;
    private readonly IEnumerable<IAmazonSimpleNotificationService> _snsServices;

    public SNSFactory(
        IOptions<SNSConfig> snsConfig,
        IEnumerable<IAmazonSimpleNotificationService> snsServices
        )
    {
        _snsConfig = snsConfig.Value;
        _snsServices = snsServices;
    }

    public IAmazonSimpleNotificationService ForDefault()
    {
        return GetSNS(_snsConfig.SNSDefaultRegion);
    }

    public IAmazonSimpleNotificationService ForSMS()
    {
        return GetSNS(_snsConfig.SNSSMSRegion);
    }

    private IAmazonSimpleNotificationService GetSNS(string region)
    {
        return GetSNS(RegionEndpoint.GetBySystemName(region));
    }

    private IAmazonSimpleNotificationService GetSNS(RegionEndpoint region)
    {
        IAmazonSimpleNotificationService service = _snsServices.FirstOrDefault(sns => sns.Config.RegionEndpoint == region);

        if (service == null)
        {
            throw new Exception($"No SNS service registered for region: {region}");
        }

        return service;
    }
}

public interface ISNSFactory
{
    IAmazonSimpleNotificationService ForDefault();

    IAmazonSimpleNotificationService ForSMS();
}

Vous pouvez maintenant obtenir le service SNS pour la région que vous souhaitez dans votre service ou contrôleur personnalisé

public class SmsSender : ISmsSender
{
    private readonly IAmazonSimpleNotificationService _sns;

    public SmsSender(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForSMS();
    }

    .......
 }

public class DeviceController : Controller
{
    private readonly IAmazonSimpleNotificationService _sns;

    public DeviceController(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForDefault();
    }

     .........
}
ArcadeRénégade
la source
5

Une approche d'usine est certainement viable. Une autre approche consiste à utiliser l'héritage pour créer des interfaces individuelles qui héritent de IService, implémenter les interfaces héritées dans vos implémentations IService et enregistrer les interfaces héritées plutôt que la base. Que l'ajout d'une hiérarchie d'héritage ou d'usines soit le «bon» modèle dépend de qui vous parlez. Je dois souvent utiliser ce modèle lorsque je traite avec plusieurs fournisseurs de bases de données dans la même application qui utilise un générique, tel queIRepository<T> , comme fondement de l'accès aux données.

Exemples d'interfaces et d'implémentations:

public interface IService 
{
}

public interface IServiceA: IService
{}

public interface IServiceB: IService
{}

public IServiceC: IService
{}

public class ServiceA: IServiceA 
{}

public class ServiceB: IServiceB
{}

public class ServiceC: IServiceC
{}

Récipient:

container.Register<IServiceA, ServiceA>();
container.Register<IServiceB, ServiceB>();
container.Register<IServiceC, ServiceC>();
jlc397
la source
5

Nécromancement.
Je pense que les gens ici réinventent la roue - et mal, si je puis dire ...
Si vous voulez enregistrer un composant par clé, utilisez simplement un dictionnaire:

System.Collections.Generic.Dictionary<string, IConnectionFactory> dict = 
    new System.Collections.Generic.Dictionary<string, IConnectionFactory>(
        System.StringComparer.OrdinalIgnoreCase);

dict.Add("ReadDB", new ConnectionFactory("connectionString1"));
dict.Add("WriteDB", new ConnectionFactory("connectionString2"));
dict.Add("TestDB", new ConnectionFactory("connectionString3"));
dict.Add("Analytics", new ConnectionFactory("connectionString4"));
dict.Add("LogDB", new ConnectionFactory("connectionString5"));

Et puis enregistrez le dictionnaire avec la collection de services:

services.AddSingleton<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(dict);

si vous ne souhaitez pas ensuite obtenir le dictionnaire et y accéder par clé, vous pouvez masquer le dictionnaire en ajoutant une méthode de recherche de clé supplémentaire à la collection de services:
(l'utilisation de délégué / fermeture devrait donner à un responsable potentiel une chance de comprendre ce qui se passe - la flèche est un peu cryptique)

services.AddTransient<Func<string, IConnectionFactory>>(
    delegate (IServiceProvider sp)
    {
        return
            delegate (string key)
            {
                System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService
 <System.Collections.Generic.Dictionary<string, IConnectionFactory>>(sp);

                if (dbs.ContainsKey(key))
                    return dbs[key];

                throw new System.Collections.Generic.KeyNotFoundException(key); // or maybe return null, up to you
            };
    });

Vous pouvez maintenant accéder à vos types avec

IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<Func<string, IConnectionFactory>>(serviceProvider)("LogDB");
logDB.Connection

ou

System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider);
dbs["logDB"].Connection

Comme nous pouvons le voir, le premier est complètement superflu, car vous pouvez également faire exactement cela avec un dictionnaire, sans nécessiter de fermetures et AddTransient (et si vous utilisez VB, même les accolades ne seront pas différentes):

IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider)["logDB"];
logDB.Connection

(plus simple est préférable - vous pouvez cependant l'utiliser comme méthode d'extension)

Bien sûr, si vous n'aimez pas le dictionnaire, vous pouvez également équiper votre interface d'une propriété Name(ou autre), et le rechercher par clé:

services.AddSingleton<IConnectionFactory>(new ConnectionFactory("ReadDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("WriteDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("TestDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("Analytics"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("LogDB"));



// /programming/39174989/how-to-register-multiple-implementations-of-the-same-interface-in-asp-net-core
services.AddTransient<Func<string, IConnectionFactory>>(
    delegate(IServiceProvider sp)
    {
        return
            delegate(string key)
            {
                System.Collections.Generic.IEnumerable<IConnectionFactory> svs = 
                    sp.GetServices<IConnectionFactory>();

                foreach (IConnectionFactory thisService in svs)
                {
                    if (key.Equals(thisService.Name, StringComparison.OrdinalIgnoreCase))
                        return thisService;
                }

                return null;
            };
    });

Mais cela nécessite de changer votre interface pour tenir compte de la propriété, et parcourir de nombreux éléments devrait être beaucoup plus lent qu'une recherche de tableau associatif (dictionnaire).
C'est bien de savoir que cela peut être fait sans dicionary, cependant.

Ce ne sont que mes 0,05 $

Stefan Steiger
la source
Si le service a été IDisposemis en œuvre, qui est responsable de l'élimination du service? Vous avez enregistré le dictionnaire en tant queSingleton
LP13
@ LP13: Vous pouvez également enregistrer un dictionnaire avec un délégué comme valeur, puis vous pouvez l'enregistrer dans itransient et créer une nouvelle instance, par exemple. GetRequiredService <T> () ["logDB"] ()
Stefan Steiger
5

depuis mon post ci-dessus, je suis passé à une classe Factory générique

Usage

 services.AddFactory<IProcessor, string>()
         .Add<ProcessorA>("A")
         .Add<ProcessorB>("B");

 public MyClass(IFactory<IProcessor, string> processorFactory)
 {
       var x = "A"; //some runtime variable to select which object to create
       var processor = processorFactory.Create(x);
 }

la mise en oeuvre

public class FactoryBuilder<I, P> where I : class
{
    private readonly IServiceCollection _services;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public FactoryBuilder(IServiceCollection services)
    {
        _services = services;
        _factoryTypes = new FactoryTypes<I, P>();
    }
    public FactoryBuilder<I, P> Add<T>(P p)
        where T : class, I
    {
        _factoryTypes.ServiceList.Add(p, typeof(T));

        _services.AddSingleton(_factoryTypes);
        _services.AddTransient<T>();
        return this;
    }
}
public class FactoryTypes<I, P> where I : class
{
    public Dictionary<P, Type> ServiceList { get; set; } = new Dictionary<P, Type>();
}

public interface IFactory<I, P>
{
    I Create(P p);
}

public class Factory<I, P> : IFactory<I, P> where I : class
{
    private readonly IServiceProvider _serviceProvider;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public Factory(IServiceProvider serviceProvider, FactoryTypes<I, P> factoryTypes)
    {
        _serviceProvider = serviceProvider;
        _factoryTypes = factoryTypes;
    }

    public I Create(P p)
    {
        return (I)_serviceProvider.GetService(_factoryTypes.ServiceList[p]);
    }
}

Extension

namespace Microsoft.Extensions.DependencyInjection
{
    public static class DependencyExtensions
    {
        public static IServiceCollection AddFactory<I, P>(this IServiceCollection services, Action<FactoryBuilder<I, P>> builder)
            where I : class
        {
            services.AddTransient<IFactory<I, P>, Factory<I, P>>();
            var factoryBuilder = new FactoryBuilder<I, P>(services);
            builder(factoryBuilder);
            return services;
        }
    }
}
T Brown
la source
Pouvez-vous fournir l'extension de méthode .AddFactory ()?
développeur
Désolé Je viens de voir ça ... ajouté
T Brown
3

Bien qu'il semble que @Miguel A. Arilla l'ait clairement indiqué et que j'ai voté pour lui, j'ai créé au-dessus de sa solution utile une autre solution qui semble soignée mais nécessite beaucoup plus de travail.

Cela dépend certainement de la solution ci-dessus. Donc, fondamentalement, j'ai créé quelque chose de similaire Func<string, IService>>et je l'ai appelé en IServiceAccessortant qu'interface, puis j'ai dû ajouter quelques extensions supplémentaires en IServiceCollectiontant que telles:

public static IServiceCollection AddSingleton<TService, TImplementation, TServiceAccessor>(
            this IServiceCollection services,
            string instanceName
        )
            where TService : class
            where TImplementation : class, TService
            where TServiceAccessor : class, IServiceAccessor<TService>
        {
            services.AddSingleton<TService, TImplementation>();
            services.AddSingleton<TServiceAccessor>();
            var provider = services.BuildServiceProvider();
            var implementationInstance = provider.GetServices<TService>().Last();
            var accessor = provider.GetServices<TServiceAccessor>().First();

            var serviceDescriptors = services.Where(d => d.ServiceType == typeof(TServiceAccessor));
            while (serviceDescriptors.Any())
            {
                services.Remove(serviceDescriptors.First());
            }

            accessor.SetService(implementationInstance, instanceName);
            services.AddSingleton<TServiceAccessor>(prvd => accessor);
            return services;
        }

L'accesseur de service ressemble à:

 public interface IServiceAccessor<TService>
    {
         void Register(TService service,string name);
         TService Resolve(string name);

    }

Le résultat final, vous pourrez enregistrer des services avec des noms ou des instances nommées comme nous le faisions avec d'autres conteneurs ... par exemple:

    services.AddSingleton<IEncryptionService, SymmetricEncryptionService, EncyptionServiceAccessor>("Symmetric");
    services.AddSingleton<IEncryptionService, AsymmetricEncryptionService, EncyptionServiceAccessor>("Asymmetric");

C'est suffisant pour l'instant, mais pour compléter votre travail, il est préférable d'ajouter plus de méthodes d'extension que vous pouvez pour couvrir tous les types d'inscriptions en suivant la même approche.

Il y avait un autre post sur stackoverflow, mais je ne le trouve pas, où l'affiche a expliqué en détail pourquoi cette fonctionnalité n'est pas prise en charge et comment la contourner, essentiellement similaire à ce que @Miguel a déclaré. C'était un bon article même si je ne suis pas d'accord avec chaque point car je pense qu'il y a des situations où vous avez vraiment besoin d'instances nommées. Je posterai ce lien ici une fois que je le retrouverai.

En fait, vous n'avez pas besoin de passer ce sélecteur ou cet accessoire:

J'utilise le code suivant dans mon projet et cela a bien fonctionné jusqu'à présent.

 /// <summary>
    /// Adds the singleton.
    /// </summary>
    /// <typeparam name="TService">The type of the t service.</typeparam>
    /// <typeparam name="TImplementation">The type of the t implementation.</typeparam>
    /// <param name="services">The services.</param>
    /// <param name="instanceName">Name of the instance.</param>
    /// <returns>IServiceCollection.</returns>
    public static IServiceCollection AddSingleton<TService, TImplementation>(
        this IServiceCollection services,
        string instanceName
    )
        where TService : class
        where TImplementation : class, TService
    {
        var provider = services.BuildServiceProvider();
        var implementationInstance = provider.GetServices<TService>().LastOrDefault();
        if (implementationInstance.IsNull())
        {
            services.AddSingleton<TService, TImplementation>();
            provider = services.BuildServiceProvider();
            implementationInstance = provider.GetServices<TService>().Single();
        }
        return services.RegisterInternal(instanceName, provider, implementationInstance);
    }

    private static IServiceCollection RegisterInternal<TService>(this IServiceCollection services,
        string instanceName, ServiceProvider provider, TService implementationInstance)
        where TService : class
    {
        var accessor = provider.GetServices<IServiceAccessor<TService>>().LastOrDefault();
        if (accessor.IsNull())
        {
            services.AddSingleton<ServiceAccessor<TService>>();
            provider = services.BuildServiceProvider();
            accessor = provider.GetServices<ServiceAccessor<TService>>().Single();
        }
        else
        {
            var serviceDescriptors = services.Where(d => d.ServiceType == typeof(IServiceAccessor<TService>));
            while (serviceDescriptors.Any())
            {
                services.Remove(serviceDescriptors.First());
            }
        }
        accessor.Register(implementationInstance, instanceName);
        services.AddSingleton<TService>(prvd => implementationInstance);
        services.AddSingleton<IServiceAccessor<TService>>(prvd => accessor);
        return services;
    }

    //
    // Summary:
    //     Adds a singleton service of the type specified in TService with an instance specified
    //     in implementationInstance to the specified Microsoft.Extensions.DependencyInjection.IServiceCollection.
    //
    // Parameters:
    //   services:
    //     The Microsoft.Extensions.DependencyInjection.IServiceCollection to add the service
    //     to.
    //   implementationInstance:
    //     The instance of the service.
    //   instanceName:
    //     The name of the instance.
    //
    // Returns:
    //     A reference to this instance after the operation has completed.
    public static IServiceCollection AddSingleton<TService>(
        this IServiceCollection services,
        TService implementationInstance,
        string instanceName) where TService : class
    {
        var provider = services.BuildServiceProvider();
        return RegisterInternal(services, instanceName, provider, implementationInstance);
    }

    /// <summary>
    /// Registers an interface for a class
    /// </summary>
    /// <typeparam name="TInterface">The type of the t interface.</typeparam>
    /// <param name="services">The services.</param>
    /// <returns>IServiceCollection.</returns>
    public static IServiceCollection As<TInterface>(this IServiceCollection services)
         where TInterface : class
    {
        var descriptor = services.Where(d => d.ServiceType.GetInterface(typeof(TInterface).Name) != null).FirstOrDefault();
        if (descriptor.IsNotNull())
        {
            var provider = services.BuildServiceProvider();
            var implementationInstance = (TInterface)provider?.GetServices(descriptor?.ServiceType)?.Last();
            services?.AddSingleton(implementationInstance);
        }
        return services;
    }
Assil
la source
1
Cela a aidé à résoudre mon problème où je perdais l'enregistrement des types dans l'accesseur de service. L'astuce consistait à supprimer toutes les liaisons pour l'accesseur de service, puis à l'ajouter à nouveau!
Umar Farooq Khawaja
3

J'ai créé une bibliothèque pour cela qui implémente de belles fonctionnalités. Le code peut être trouvé sur GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/

L'utilisation est simple:

  1. Ajoutez le package de nuget Dazinator.Extensions.DependencyInjection à votre projet.
  2. Ajoutez vos inscriptions au service nommé.
    var services = new ServiceCollection();
    services.AddNamed<AnimalService>(names =>
    {
        names.AddSingleton("A"); // will resolve to a singleton instance of AnimalService
        names.AddSingleton<BearService>("B"); // will resolve to a singleton instance of BearService (which derives from AnimalService)
        names.AddSingleton("C", new BearService()); will resolve to singleton instance provided yourself.
        names.AddSingleton("D", new DisposableTigerService(), registrationOwnsInstance = true); // will resolve to singleton instance provided yourself, but will be disposed for you (if it implements IDisposable) when this registry is disposed (also a singleton).

        names.AddTransient("E"); // new AnimalService() every time..
        names.AddTransient<LionService>("F"); // new LionService() every time..

        names.AddScoped("G");  // scoped AnimalService
        names.AddScoped<DisposableTigerService>("H");  scoped DisposableTigerService and as it implements IDisposable, will be disposed of when scope is disposed of.

    });

Dans l'exemple ci-dessus, notez que pour chaque enregistrement nommé, vous spécifiez également la durée de vie ou Singleton, Scoped ou Transient.

Vous pouvez résoudre les services de deux manières, selon que vous êtes à l'aise avec le fait que vos services dépendent de ce package de non:

public MyController(Func<string, AnimalService> namedServices)
{
   AnimalService serviceA = namedServices("A");
   AnimalService serviceB = namedServices("B"); // BearService derives from AnimalService
}

ou

public MyController(NamedServiceResolver<AnimalService> namedServices)
{
   AnimalService serviceA = namedServices["A"];
   AnimalService serviceB = namedServices["B"]; // instance of BearService returned derives from AnimalService
}

J'ai spécifiquement conçu cette bibliothèque pour bien fonctionner avec Microsoft.Extensions.DependencyInjection - par exemple:

  1. Lorsque vous enregistrez des services nommés, tous les types que vous enregistrez peuvent avoir des constructeurs avec des paramètres - ils seront satisfaits via DI, de la même manière que AddTransient<>, AddScoped<>et les AddSingleton<>méthodes fonctionnent normalement.

  2. Pour les services nommés transitoires et étendus, le registre crée un ObjectFactoryafin qu'il puisse activer de nouvelles instances du type très rapidement en cas de besoin. Ceci est beaucoup plus rapide que les autres approches et correspond à la façon dont Microsoft.Extensions.DependencyInjection fait les choses.

Darrell
la source
2

Ma solution pour ce que ça vaut ... a envisagé de passer à Castle Windsor car je ne peux pas dire que j'ai aimé l'une des solutions ci-dessus. Désolé!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

Créez vos différentes implémentations

public class YourClassA : IStage<YouClassA> { 
    public void DoSomething() 
    {
        ...TODO
    }
}

public class YourClassB : IStage<YourClassB> { .....etc. }

enregistrement

services.AddTransient<IStage<YourClassA>, YourClassA>()
services.AddTransient<IStage<YourClassB>, YourClassB>()

Utilisation du constructeur et de l'instance ...

public class Whatever
{
   private IStage ClassA { get; }

   public Whatever(IStage<YourClassA> yourClassA)
   {
         ClassA = yourClassA;
   }

   public void SomeWhateverMethod()
   {
        ClassA.DoSomething();
        .....
   }
petit
la source
1

Extension de la solution de @rnrneverdies. Au lieu de ToString (), les options suivantes peuvent également être utilisées: 1) Avec une implémentation de propriété commune, 2) Un service de services proposé par @Craig Brunetti.

public interface IService { }
public class ServiceA : IService
{
    public override string ToString()
    {
        return "A";
    }
}

public class ServiceB : IService
{
    public override string ToString()
    {
        return "B";
    }

}

/// <summary>
/// extension method that compares with ToString value of an object and returns an object if found
/// </summary>
public static class ServiceProviderServiceExtensions
{
    public static T GetService<T>(this IServiceProvider provider, string identifier)
    {
        var services = provider.GetServices<T>();
        var service = services.FirstOrDefault(o => o.ToString() == identifier);
        return service;
    }
}

public void ConfigureServices(IServiceCollection services)
{
    //Initials configurations....

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

    var sp = services.BuildServiceProvider();
    var a = sp.GetService<IService>("A"); //returns instance of ServiceA
    var b = sp.GetService<IService>("B"); //returns instance of ServiceB

    //Remaining configurations....
}
vrluckyin
la source
1

Après avoir lu les réponses ici et des articles ailleurs, j'ai pu le faire fonctionner sans chaînes. Lorsque vous avez plusieurs implémentations de la même interface, la DI les ajoutera à une collection, il est donc possible de récupérer la version souhaitée de la collection à l'aide typeof.

// In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(IService, ServiceA);
    services.AddScoped(IService, ServiceB);
    services.AddScoped(IService, ServiceC);
}

// Any class that uses the service(s)
public class Consumer
{
    private readonly IEnumerable<IService> _myServices;

    public Consumer(IEnumerable<IService> myServices)
    {
        _myServices = myServices;
    }

    public UseServiceA()
    {
        var serviceA = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceA));
        serviceA.DoTheThing();
    }

    public UseServiceB()
    {
        var serviceB = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceB));
        serviceB.DoTheThing();
    }

    public UseServiceC()
    {
        var serviceC = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceC));
        serviceC.DoTheThing();
    }
}
Ciaran Bruen
la source
Défait le but de l'IoC. Autant écrire:var serviceA = new ServiceA();
James Curran
2
@JamesCurran pas si ServiceA a des dépendances, ou si vous voulez tester unitaire la classe.
Jorn.Beyers
0

Bien que l'implémentation prête à l'emploi ne le propose pas, voici un exemple de projet qui vous permet d'enregistrer des instances nommées, puis d'injecter INamedServiceFactory dans votre code et d'extraire des instances par nom. Contrairement aux autres solutions facory ici, cela vous permettra d'enregistrer plusieurs instances de la même implémentation mais configurées différemment

https://github.com/macsux/DotNetDINamedInstances

Andrew Stakhov
la source
0

Que diriez-vous d'un service pour des services?

Si nous avions une interface INamedService (avec la propriété .Name), nous pourrions écrire une extension IServiceCollection pour .GetService (nom de chaîne), où l'extension prendrait ce paramètre de chaîne et ferait un .GetServices () sur lui-même et dans chacun d'eux retourné instance, recherchez l'instance dont INamedService.Name correspond au nom donné.

Comme ça:

public interface INamedService
{
    string Name { get; }
}

public static T GetService<T>(this IServiceProvider provider, string serviceName)
    where T : INamedService
{
    var candidates = provider.GetServices<T>();
    return candidates.FirstOrDefault(s => s.Name == serviceName);
}

Par conséquent, votre IMyService doit implémenter INamedService, mais vous obtiendrez la résolution basée sur les clés que vous souhaitez, non?

Pour être honnête, devoir même avoir cette interface INamedService semble moche, mais si vous vouliez aller plus loin et rendre les choses plus élégantes, alors un [NamedServiceAttribute ("A")] sur l'implémentation / classe pourrait être trouvé par le code dans ce extension, et cela fonctionnerait tout aussi bien. Pour être encore plus juste, la réflexion est lente, donc une optimisation peut être nécessaire, mais honnêtement, c'est quelque chose que le moteur DI aurait dû aider. La rapidité et la simplicité sont chacune de grands contributeurs au TCO.

Dans l'ensemble, il n'y a pas besoin d'une usine explicite, car «trouver un service nommé» est un concept tellement réutilisable, et les classes d'usine ne sont pas évolutives comme solution. Et un Func <> semble bien, mais un bloc de commutateurs est tellement bluffant , et encore une fois, vous écrirez des Funcs aussi souvent que vous écrivez des usines. Commencez simple, réutilisable, avec moins de code, et si cela s'avère ne pas le faire pour vous, alors devenez complexe.

Craig Brunetti
la source
2
C'est ce que l'on appelle le modèle de localisateur de service et ce n'est généralement pas la meilleure route à suivre, sauf si vous devez absolument le faire
Joe Phillips
@JoePhillips Avez-vous une idée de pourquoi ce n'est pas une bonne solution? j'adore son élégance. Le seul inconvénient auquel je peux penser est que je crée une instance de chacun d'eux à chaque fois que vous en obtenez un.
Peter
2
@Peter La raison principale est qu'il est très très difficile de travailler avec. Si vous passez un objet serviceLocator dans une classe, il n'est pas du tout évident quelles dépendances cette classe utilise car elle les obtient toutes d'un objet magique "dieu". Imaginez devoir trouver des références du type que vous souhaitez modifier. Cette capacité disparaît essentiellement lorsque vous obtenez tout via un objet de localisation de service. L'injection constructeur est beaucoup plus claire et fiable
Joe Phillips
Je ne sais pas. L'évidence n'est pas un inconvénient pour moi ... parce que si je tenais à garder une trace de la façon dont mes composants exploitent leurs dépendances, j'aurais des tests unitaires pour cela ... des tests qui se réfèrent non seulement à chaque dépendance, mais nous aident à comprendre COMMENT chaque dépendance est nécessaire. Sinon, comment allez-vous en être conscient, en lisant les constructeurs?!?
Craig Brunetti du
0

J'ai rencontré le même problème et j'ai travaillé avec une simple extension pour autoriser les services nommés. Vous pouvez le trouver ici:

Il vous permet d'ajouter autant de services (nommés) que vous le souhaitez comme ceci:

 var serviceCollection = new ServiceCollection();
 serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), "A", ServiceLifetime.Transient);
 serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), "B", ServiceLifetime.Transient);

 var serviceProvider = serviceCollection.BuildServiceProvider();

 var myServiceA = serviceProvider.GetService<IMyService>("A");
 var myServiceB = serviceProvider.GetService<IMyService>("B");

La bibliothèque vous permet également d'implémenter facilement un "modèle d'usine" comme ceci:

    [Test]
    public void FactoryPatternTest()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), MyEnum.A.GetName(), ServiceLifetime.Transient);
        serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), MyEnum.B.GetName(), ServiceLifetime.Transient);

        serviceCollection.AddTransient<IMyServiceFactoryPatternResolver, MyServiceFactoryPatternResolver>();

        var serviceProvider = serviceCollection.BuildServiceProvider();

        var factoryPatternResolver = serviceProvider.GetService<IMyServiceFactoryPatternResolver>();

        var myServiceA = factoryPatternResolver.Resolve(MyEnum.A);
        Assert.NotNull(myServiceA);
        Assert.IsInstanceOf<MyServiceA>(myServiceA);

        var myServiceB = factoryPatternResolver.Resolve(MyEnum.B);
        Assert.NotNull(myServiceB);
        Assert.IsInstanceOf<MyServiceB>(myServiceB);
    }

    public interface IMyServiceFactoryPatternResolver : IFactoryPatternResolver<IMyService, MyEnum>
    {
    }

    public class MyServiceFactoryPatternResolver : FactoryPatternResolver<IMyService, MyEnum>, IMyServiceFactoryPatternResolver
    {
        public MyServiceFactoryPatternResolver(IServiceProvider serviceProvider)
        : base(serviceProvider)
        {
        }
    }

    public enum MyEnum
    {
        A = 1,
        B = 2
    }

J'espère que ça aide

Subgurim
la source
0

J'ai créé ma propre extension par rapport à l' extension IServiceCollectionutilisée WithName:

public static IServiceCollection AddScopedWithName<TService, TImplementation>(this IServiceCollection services, string serviceName)
        where TService : class
        where TImplementation : class, TService
    {
        Type serviceType = typeof(TService);
        Type implementationServiceType = typeof(TImplementation);
        ServiceCollectionTypeMapper.Instance.AddDefinition(serviceType.Name, serviceName, implementationServiceType.AssemblyQualifiedName);
        services.AddScoped<TImplementation>();
        return services;
    }

ServiceCollectionTypeMapperest une instance singleton que les cartes IService> NameOfService> Implementationoù une interface pourrait avoir de nombreuses implémentations avec des noms différents, ce qui permet d'enregistrer les types que nous pouvons résoudre quand wee besoin et est une approche différente de résoudre de multiples services pour choisir ce que nous voulons.

 /// <summary>
/// Allows to set the service register mapping.
/// </summary>
public class ServiceCollectionTypeMapper
{
    private ServiceCollectionTypeMapper()
    {
        this.ServiceRegister = new Dictionary<string, Dictionary<string, string>>();
    }

    /// <summary>
    /// Gets the instance of mapper.
    /// </summary>
    public static ServiceCollectionTypeMapper Instance { get; } = new ServiceCollectionTypeMapper();

    private Dictionary<string, Dictionary<string, string>> ServiceRegister { get; set; }

    /// <summary>
    /// Adds new service definition.
    /// </summary>
    /// <param name="typeName">The name of the TService.</param>
    /// <param name="serviceName">The TImplementation name.</param>
    /// <param name="namespaceFullName">The TImplementation AssemblyQualifiedName.</param>
    public void AddDefinition(string typeName, string serviceName, string namespaceFullName)
    {
        if (this.ServiceRegister.TryGetValue(typeName, out Dictionary<string, string> services))
        {
            if (services.TryGetValue(serviceName, out _))
            {
                throw new InvalidOperationException($"Exists an implementation with the same name [{serviceName}] to the type [{typeName}].");
            }
            else
            {
                services.Add(serviceName, namespaceFullName);
            }
        }
        else
        {
            Dictionary<string, string> serviceCollection = new Dictionary<string, string>
            {
                { serviceName, namespaceFullName },
            };
            this.ServiceRegister.Add(typeName, serviceCollection);
        }
    }

    /// <summary>
    /// Get AssemblyQualifiedName of implementation.
    /// </summary>
    /// <typeparam name="TService">The type of the service implementation.</typeparam>
    /// <param name="serviceName">The name of the service.</param>
    /// <returns>The AssemblyQualifiedName of the inplementation service.</returns>
    public string GetService<TService>(string serviceName)
    {
        Type serviceType = typeof(TService);

        if (this.ServiceRegister.TryGetValue(serviceType.Name, out Dictionary<string, string> services))
        {
            if (services.TryGetValue(serviceName, out string serviceImplementation))
            {
                return serviceImplementation;
            }
            else
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }

Pour enregistrer un nouveau service:

services.AddScopedWithName<IService, MyService>("Name");

Pour résoudre le service, nous avons besoin d'une extension IServiceProvidercomme celle-ci.

/// <summary>
    /// Gets the implementation of service by name.
    /// </summary>
    /// <typeparam name="T">The type of service.</typeparam>
    /// <param name="serviceProvider">The service provider.</param>
    /// <param name="serviceName">The service name.</param>
    /// <returns>The implementation of service.</returns>
    public static T GetService<T>(this IServiceProvider serviceProvider, string serviceName)
    {
        string fullnameImplementation = ServiceCollectionTypeMapper.Instance.GetService<T>(serviceName);
        if (fullnameImplementation == null)
        {
            throw new InvalidOperationException($"Unable to resolve service of type [{typeof(T)}] with name [{serviceName}]");
        }
        else
        {
            return (T)serviceProvider.GetService(Type.GetType(fullnameImplementation));
        }
    }

Lorsque résoudre:

serviceProvider.GetService<IWithdrawalHandler>(serviceName);

N'oubliez pas que serviceProvider peut être injecté dans un constructeur dans notre application en tant que IServiceProvider.

J'espère que ça aide.

svladimirrc
la source