it-swarm.com.de

Wie registrieren Sie mehrere Implementierungen derselben Schnittstelle in Asp.Net Core?

Ich habe Dienste, die von derselben Schnittstelle abgeleitet sind

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

Typischerweise ermöglichen andere IOC -Container wie Unity die Registrierung konkreter Implementierungen durch einige Key, die sie unterscheiden.

Wie registriere ich diese Dienste in Asp.Net Core und löse sie zur Laufzeit basierend auf einem Schlüssel auf? 

Ich sehe keinen der Add Service-Methoden, die key- oder name-Parameter verwenden, mit denen normalerweise die konkrete Implementierung unterschieden wird.

    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
       }
    }

Ist das Fabrikmuster die einzige Option hier?

Update1
Ich habe den Artikel here durchgegangen, der zeigt, wie Factory-Muster verwendet werden, um Service-Instanzen abzurufen, wenn wir mehrere Concreate-Implementierungen haben. Es ist jedoch immer noch keine vollständige Lösung. Wenn ich die _serviceProvider.GetService()-Methode aufrufe, kann ich keine Daten in den Konstruktor einfügen. Betrachten Sie zum Beispiel dieses Beispiel

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;
    }
}

Wie kann _serviceProvider.GetService() eine geeignete Verbindungszeichenfolge einfügen? In Unity oder einem anderen IOC können wir das zum Zeitpunkt der Typregistrierung tun. Ich kann IOption verwenden das erfordert jedoch von mir, dass alle Einstellungen eingefügt werden. Ich kann keine bestimmte Verbindungszeichenfolge in den Dienst einfügen.

Beachten Sie auch, dass ich versuche, andere Container (einschließlich Unity) zu verwenden, da ich dann auch alle anderen Container (z. B. Controller) mit dem neuen Container registrieren muss.

Die Verwendung des Factory-Patterns zur Erstellung der Service-Instanz ist gegen DIP, da Factory die Anzahl der Abhängigkeiten erhöht, die ein Client von Details hier abhängig machen muss.

Ich denke also, dass der Standard-DI in ASP.NET-Kern 2 Dinge vermisst
1> Instanzen mit Schlüssel registrieren
2> Statische Daten während der Registrierung in Konstruktor einfügen 

124
LP13

Ich habe eine einfache Problemumgehung mit Func durchgeführt, als ich mich in dieser Situation befand.

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
    }
});

Und verwenden Sie es aus jeder Klasse, die bei DI registriert ist, wie:

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();
    }
}

UPDATE

Beachten Sie, dass in diesem Beispiel der Schlüssel für die Auflösung eine Zeichenfolge ist, der Einfachheit halber und weil OP speziell für diesen Fall gefordert hat.

Sie können jedoch jeden benutzerdefinierten Auflösungstyp als Schlüssel verwenden, da Sie normalerweise nicht möchten, dass ein großer n-case-Schalter Ihren Code verrottet. Kommt drauf an, wie Ihre App skaliert.

110

Eine andere Option ist die Verwendung der Erweiterungsmethode GetServices aus Microsoft.Extensions.DependencyInjection.

Registrieren Sie Ihre Dienstleistungen als: 

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

Dann mit ein wenig Linq auflösen:

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

oder 

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

(vorausgesetzt, dass IService eine String-Eigenschaft namens "Name" hat)

Stellen Sie sicher, dass Sie using Microsoft.Extensions.DependencyInjection; haben.

Aktualisieren

AspNet 2.1-Quelle: GetServices

46
rnrneverdies

Es wird nicht von Microsoft.Extensions.DependencyInjection unterstützt.

Sie können jedoch auch einen anderen Abhängigkeitsinjektionsmechanismus einfügen, wie StructureMapSiehe Homepage und GitHub-Projekt .

Es ist überhaupt nicht schwer:

  1. Fügen Sie StructureMap in Ihrem project.json eine Abhängigkeit hinzu:

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
    
  2. Injizieren Sie es in die ASP.NET-Pipeline in ConfigureServices und registrieren Sie Ihre Klassen (siehe 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. Um eine benannte Instanz zu erhalten, müssen Sie die Variable IContainer anfordern.

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

Das ist es.

Um das Beispiel zu bauen, brauchen Sie

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

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

        public string Name {get; set; }
    }
15

Sie sind richtig, der eingebaute ASP.NET Core-Container hat nicht das Konzept, mehrere Dienste zu registrieren und anschließend einen bestimmten Dienst abzurufen. Eine Factory ist in diesem Fall die einzig echte Lösung.

Alternativ können Sie zu einem Drittanbietercontainer wie Unity oder StructureMap wechseln, der die von Ihnen benötigte Lösung bereitstellt (dokumentiert hier: https://docs.asp.net/de/latest/fundamentals/dependency-injection.html?# Ersetzen des Standardservices-Containers ).

10
Sock

Ich habe das gleiche Problem gesehen und möchte mitteilen, wie ich es gelöst habe und warum.

Wie Sie erwähnt haben, gibt es zwei Probleme. Der Erste:

Wie registriere ich diese Dienste in Asp.Net Core und löse sie unter .__ auf. Laufzeit basierend auf einem Schlüssel?

Welche Möglichkeiten haben wir also? Die Leute schlagen zwei vor:

  • Verwenden Sie eine benutzerdefinierte Fabrik (wie _myFactory.GetServiceByKey(key))

  • Verwenden Sie eine andere DI-Engine (wie _unityContainer.Resolve<IService>(key))

Ist das Fabrikmuster die einzige Option hier?

Tatsächlich sind beide Optionen Fabriken, da jeder IoC-Container auch eine Fabrik ist (hoch konfigurierbar und kompliziert). Und es scheint mir, dass andere Optionen auch Variationen des Factory-Musters sind.

Welche Option ist dann besser? Hier stimme ich @Sock zu, der vorschlug, Custom Factory zu verwenden, und deshalb.

Erstens versuche ich immer, neue Abhängigkeiten hinzuzufügen, wenn diese nicht wirklich benötigt werden. Deshalb stimme ich Ihnen in diesem Punkt zu. Darüber hinaus ist die Verwendung von zwei DI-Frameworks schlechter als das Erstellen einer benutzerdefinierten Factory-Abstraktion. Im zweiten Fall müssen Sie eine neue Paketabhängigkeit hinzufügen (wie Unity), aber abhängig von einer neuen Factory-Schnittstelle ist hier weniger übel. Ich glaube, die Hauptidee von ASP.NET Core DI ist Einfachheit. Es unterhält ein Minimum an Funktionen nach dem KISS Prinzip . Wenn Sie ein zusätzliches Feature benötigen, dann setzen Sie auf DIY oder verwenden Sie ein entsprechendes Plungin , das das gewünschte Feature implementiert (Open Closed-Prinzip).

Zweitens müssen wir oft viele benannte Abhängigkeiten für einen einzelnen Dienst einfügen. Bei Unity müssen Sie möglicherweise Namen für Konstruktorparameter angeben (mithilfe von InjectionConstructor). Diese Registrierung verwendet Reflektion und einige intelligente Logik, um Argumente für den Konstruktor zu erraten. Dies kann ebenfalls zu Laufzeitfehlern führen, wenn die Registrierung nicht mit den Konstruktorargumenten übereinstimmt. Auf der anderen Seite haben Sie bei der Verwendung Ihrer eigenen Fabrik die volle Kontrolle über die Bereitstellung der Konstruktorparameter. Es ist lesbarer und zur Kompilierzeit aufgelöst. KISS Prinzip noch einmal.

Das zweite Problem:

Wie kann _serviceProvider.GetService () die entsprechende Verbindung einfügen String?

Erstens stimme ich Ihnen zu, dass es abhängig von neuen Dingen wie IOptions (und daher vom Paket Microsoft.Extensions.Options.ConfigurationExtensions) keine gute Idee ist. Ich habe einige Diskussionen über IOptions gesehen, wo es unterschiedliche Meinungen zu ihrem Nutzen gab. Ich versuche wieder zu vermeiden, neue Abhängigkeiten hinzuzufügen, wenn sie nicht wirklich benötigt werden. Ist es wirklich nötig? Ich denke nicht. Ansonsten müsste jede Implementierung davon abhängen, ohne dass eine klare Notwendigkeit dafür besteht (für mich ist dies eine Verletzung des ISP, bei der ich auch mit Ihnen einverstanden bin). Dies gilt auch je nach Fabrik, aber in diesem Fall wird can vermieden.

Der ASP.NET Core DI bietet zu diesem Zweck eine sehr schöne Überladung:

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

Ich spritze einfach ein IEnumerable ein

ConfigureServices in Startup.cs

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

Services-Ordner

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();
    }
...
}

Erweiterungen.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;
    }
4
T Brown

Anscheinend können Sie einfach IEnumerable Ihrer Service-Schnittstelle einbinden! Und dann die gewünschte Instanz mit LINQ finden.

Mein Beispiel ist für den AWS SNS-Dienst, aber Sie können dasselbe auch für jeden injizierten Dienst tun.

Anlaufen

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",

SNS Factory

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();
}

Jetzt können Sie den SNS-Dienst für die Region in Ihrem benutzerdefinierten Dienst oder Controller erhalten

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();
    }

     .........
}
3
ArcadeRenegade

Obwohl es so scheint, als habe @ Miguel A. Arilla klar darauf hingewiesen und ich habe für ihn gestimmt, habe ich zusätzlich zu seiner nützlichen Lösung eine weitere Lösung geschaffen, die ordentlich aussieht, aber viel mehr Arbeit erfordert.

Es hängt definitiv von der obigen Lösung ab. Im Grunde habe ich etwas Ähnliches wie Func<string, IService>> erstellt und es als Schnittstelle IServiceAccessor bezeichnet. Dann musste ich einige weitere Erweiterungen zu IServiceCollection hinzufügen: 

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;
        }

Der Dienst Accessor sieht folgendermaßen aus:

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

    }

Als Endergebnis können Sie Services mit Namen oder benannten Instanzen registrieren, wie wir es von anderen Containern gewohnt sind.

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

Das reicht fürs Erste, aber um Ihre Arbeit abzuschließen, ist es besser, weitere Erweiterungsmethoden hinzuzufügen, um alle Arten von Registrierungen nach demselben Ansatz abzudecken.

Es gab einen weiteren Beitrag zu stackoverflow, aber ich kann ihn nicht finden, wo das Poster im Detail erklärt hat, warum diese Funktion nicht unterstützt wird und wie man sie umgehen kann, im Grunde ähnlich wie bei @Miguel. Es war ein schöner Beitrag, auch wenn ich mit jedem Punkt nicht einverstanden bin, weil es meiner Meinung nach Situationen gibt, in denen Sie wirklich benannte Instanzen benötigen. Ich werde diesen Link hier posten, sobald ich ihn wieder finde.

Sie müssen diesen Selector oder Accessor nicht übergeben:

Ich verwende den folgenden Code in meinem Projekt und es hat bisher gut funktioniert.

 /// <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;
    }
2
Assil

Meine Lösung für das, was es wert ist ... Ich habe es mir überlegt, zu Castle Windsor zu wechseln, da ich nicht sagen kann, dass mir eine der oben genannten Lösungen gefallen hat. Es tut uns leid!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

Erstellen Sie Ihre verschiedenen Implementierungen

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

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

Anmeldung

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

Konstruktor- und Instanznutzung ...

public class Whatever
{
   private IStage ClassA { get; }

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

   public void SomeWhateverMethod()
   {
        ClassA.DoSomething();
        .....
   }
1
littgle

Die meisten Antworten verstoßen hier gegen das Prinzip der einmaligen Verantwortung (eine Serviceklasse sollte Abhängigkeiten nicht selbst auflösen) und/oder verwenden das Anti-Pattern des Service-Locators.

Eine andere Möglichkeit, diese Probleme zu vermeiden, besteht darin,

  • verwenden Sie einen zusätzlichen generischen Typparameter für die Schnittstelle oder eine neue Schnittstelle, die die nicht generische Schnittstelle implementiert.
  • implementieren Sie eine Adapter-/Interceptor-Klasse, um den Markertyp hinzuzufügen
  • benutze den generischen Typ als "Name"

Ich habe einen Artikel mit weiteren Details geschrieben: Abhängigkeitsinjektion in .NET: Eine Möglichkeit, fehlende benannte Registrierungen zu umgehen

1
Rico Suter

Nekromanzierung.
Ich denke, die Leute hier erfinden das Rad neu - und, wenn ich so sagen darf, schlecht ...
Wenn Sie eine Komponente nach Schlüssel registrieren möchten, verwenden Sie einfach ein Wörterbuch:

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"));

Und dann registrieren Sie das Wörterbuch mit der Service-Sammlung:

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

wenn Sie dann nicht bereit sind, das Wörterbuch abzurufen und über einen Schlüssel darauf zuzugreifen, können Sie das Wörterbuch ausblenden, indem Sie der Service-Sammlung eine zusätzliche Methode zur Schlüsselsuche hinzufügen:
(Die Verwendung von delegate/closure sollte einem potenziellen Betreuer die Möglichkeit geben, zu verstehen, was vor sich geht - die Pfeilnotation ist ein bisschen kryptisch.)

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
            };
    });

Jetzt können Sie entweder mit auf Ihre Typen zugreifen

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

oder

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

Wie wir sehen können, ist die erste Version völlig überflüssig, da Sie genau das auch mit einem Wörterbuch tun können, ohne dass Closures und AddTransient erforderlich sind (und wenn Sie VB verwenden, werden nicht einmal die Klammern unterschiedlich sein):

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

(Einfacher ist besser - vielleicht möchten Sie es aber als Erweiterungsmethode verwenden)

Wenn Ihnen das Wörterbuch nicht gefällt, können Sie Ihre Benutzeroberfläche natürlich auch mit einer Eigenschaft Name (oder was auch immer) ausstatten und das mit dem Schlüssel nachschlagen:

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"));



// https://stackoverflow.com/questions/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;
            };
    });

Dies erfordert jedoch eine Änderung der Benutzeroberfläche, um die Eigenschaft zu berücksichtigen, und das Durchlaufen vieler Elemente sollte wesentlich langsamer sein als das Nachschlagen eines assoziativen Arrays (Wörterbuch).
Schön zu wissen, dass es auch ohne Wörterbuch geht.

Dies sind nur meine 0,05 $

0
Stefan Steiger

Etwas spät zu dieser Party, aber hier ist meine Lösung: ...

Startup.cs oder Program.cs wenn generischer Handler ...

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

IMyInterface des T-Schnittstellen-Setups

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

Konkrete Implementierungen von IMyInterface von T

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

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

Wenn es Probleme gibt, wird es hoffentlich jemand darauf hinweisen, warum dies der falsche Weg ist. 

0
Gray

Ein Fabrikansatz ist durchaus möglich. Ein weiterer Ansatz besteht darin, Vererbung zu verwenden, um einzelne Schnittstellen zu erstellen, die von IService erben, die geerbten Schnittstellen in Ihren IService-Implementierungen zu implementieren und die geerbten Schnittstellen anstelle der Basis zu registrieren. Ob das Hinzufügen einer Vererbungshierarchie oder von Fabriken das "richtige" Muster ist, hängt alles davon ab, mit wem Sie sprechen. Ich muss dieses Muster häufig verwenden, wenn ich mit mehreren Datenbankanbietern in derselben Anwendung arbeite, die ein generisches Element wie IRepository<T> als Grundlage für den Datenzugriff verwendet. 

Beispielschnittstellen und Implementierungen:

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
{}

Container:

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

Erweiterung der Lösung von @rnrneverdies. Anstelle von ToString () können auch die folgenden Optionen verwendet werden: 1) Mit Implementierung gemeinsamer Eigenschaften, 2) Ein von @Craig Brunetti vorgeschlagener Dienst.

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....
}
0
vrluckyin

Während die Standardimplementierung dies nicht bietet, können Sie hier benannte Instanzen registrieren und dann INamedServiceFactory in Ihren Code einfügen und Instanzen nach Namen abrufen. Im Gegensatz zu anderen Facory-Lösungen können Sie hier mehrere Instanzen von gleicher Implementierung registrieren, jedoch anders konfigurieren

https://github.com/macsux/DotNetDINamedInstances

0
Andrew Stakhov

Wie wäre es mit einem Service für Services?

Wenn wir eine INamedService-Schnittstelle (mit der .Name-Eigenschaft) hätten, könnten wir eine IServiceCollection-Erweiterung für .GetService (Zeichenfolgenname) schreiben, wobei die Erweiterung diesen String-Parameter verwenden würde und ein .GetServices () für sich selbst und in jedem zurückgegebenen Befehl ausführen würde Instanz finden Sie die Instanz, deren INamedService.Name mit dem angegebenen Namen übereinstimmt.

So was:

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);
}

Daher muss Ihr IMyService INamedService implementieren, aber Sie erhalten die von Ihnen gewünschte schlüsselbasierte Auflösung, oder?

Um ehrlich zu sein, muss dieses INamedService-Interface sogar hässlich sein. Wenn Sie jedoch weiter vorgehen und die Dinge eleganter gestalten möchten, könnte ein [NamedServiceAttribute ("A")] über die Implementierung/Klasse durch den Code in diesem Code gefunden werden Erweiterung, und es würde genauso gut funktionieren. Um noch fairer zu sein, ist Reflection langsam, daher ist eine Optimierung möglicherweise in Ordnung, aber ehrlich gesagt, sollte der DI-Motor dabei geholfen haben. Schnelligkeit und Einfachheit tragen maßgeblich zur Gesamtbetriebskosten bei.

Alles in allem ist keine explizite Factory erforderlich, da das "Finden eines benannten Services" ein solches wiederverwendbares Konzept ist und Factory-Klassen nicht als Lösung skalierbar sind. Und ein Func scheint gut zu sein, aber ein Schalterblock ist so bleh , und wieder werden Sie Funcs so oft schreiben, wie Sie Factories schreiben würden. Beginnen Sie einfach, wiederverwendbar, mit weniger Code, und wenn sich herausstellt, dass Sie dies für Sie nicht tun, dann gehen Sie komplex.

0
Craig Brunetti