Come registrare più implementazioni della stessa interfaccia in Asp.Net Core?


240

Ho servizi derivati ​​dalla stessa interfaccia.

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

In genere, altri contenitori IoC come Unityconsentono di registrare implementazioni concrete da parte di alcuni Keyche le distinguono.

In ASP.NET Core, come posso registrare questi servizi e risolverli in fase di esecuzione in base a una chiave?

Non vedo alcun Addmetodo di servizio che accetta un keyo un nameparametro, che in genere verrebbe utilizzato per distinguere l'implementazione concreta.

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

Il modello Factory è l'unica opzione qui?

Aggiornamento 1
Ho esaminato l'articolo qui che mostra come utilizzare il modello di fabbrica per ottenere istanze di servizio quando disponiamo di più implementazioni concrete. Tuttavia, non è ancora una soluzione completa. Quando chiamo il _serviceProvider.GetService()metodo, non riesco a iniettare dati nel costruttore.

Ad esempio, considera questo:

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

Come si può _serviceProvider.GetService()iniettare la stringa di connessione appropriata? In Unity o in qualsiasi altra libreria IoC, possiamo farlo al momento della registrazione del tipo. Posso usare IOption , tuttavia, che mi richiederà di iniettare tutte le impostazioni. Non riesco a iniettare una particolare stringa di connessione nel servizio.

Si noti inoltre che sto cercando di evitare di utilizzare altri contenitori (incluso Unity) perché in seguito devo registrare tutto il resto (ad esempio, i controller) con il nuovo contenitore.

Inoltre, l'utilizzo del modello factory per creare istanze di servizio è contrario a DIP, in quanto aumenta il numero di dipendenze che un cliente ha dettagli qui .

Quindi, penso che al DI predefinito in ASP.NET Core manchino due cose:

  1. La possibilità di registrare istanze utilizzando una chiave
  2. La possibilità di iniettare dati statici nei costruttori durante la registrazione

4
Possibile duplicato dell'iniezione
adem caglin,

2
C'è finalmente un'estensione in nuget per le registrazioni basate sul nome, spero che possa aiutare
neleus

Ciao, scusami per la mia stupida domanda, ma sto parlando di Microsoft.Extensions.DependencyInjection ... pensi che crei 3 interfacce vuote che estendono Iservice come "public interface IServiceA: IService" e che "public class ServiceA: IServiceA "... potrebbe essere un'opzione di buona pratica?
Emiliano Magliocca,

questo articolo è di qualche utilità? stevejgordon.co.uk/…
Mike B,

Può Update1essere spostato su una domanda diversa poiché iniettare cose nei costruttori è molto diverso dal capire quale oggetto costruire
Neil

Risposte:


246

Ho fatto una semplice soluzione usando Funcquando mi sono trovato in questa situazione.

Dichiara innanzitutto un delegato condiviso:

public delegate IService ServiceResolver(string key);

Quindi, nel tuo Startup.cs, imposta le molteplici registrazioni concrete e una mappatura manuale di questi tipi:

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

E usalo da qualsiasi classe registrata con DI:

public class Consumer
{
    private readonly IService _aService;

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

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

Tieni presente che in questo esempio la chiave per la risoluzione è una stringa, per semplicità e perché OP ha richiesto questo caso in particolare.

Ma potresti usare qualsiasi tipo di risoluzione personalizzato come chiave, poiché di solito non vuoi che un enorme switch n-case marcisca il tuo codice. Dipende da come viene ridimensionata l'app.


1
@MatthewStevenMonkan ha aggiornato la mia risposta con un esempio
Miguel A. Arilla,

2
Utilizzare un modello di fabbrica come questo è il modo migliore per procedere. Grazie per la condivisione!
Sergey Akopov,

2
+1 Molto pulito e ordinato, perché quando utilizziamo un altro contenitore separato dobbiamo includere il loro pacchetto ogni volta che dobbiamo risolvere le dipendenze, ad es. ILifetimeScope in AutoFac.
Anupam Singh,

1
@AnupamSingh Secondo me, la maggior parte dei tipi di applicazioni medio-piccole in esecuzione su .NET Core non hanno bisogno di alcun framework DI, aggiunge solo complessità e dipendenze indesiderate, la bellezza e la semplicità del DI integrato è più che sufficiente e può anche essere esteso con facilità.
Miguel A. Arilla,

7
Spiegazione del voto negativo: è molto interessante, ma al momento sto eseguendo il refactoring di un'enorme base di codice per rimuovere tutta questa magia di Func che qualcuno ha fatto qualche anno fa (prima della rivoluzione MS DI) Il problema è che aumenta notevolmente la complessità della connascenza sulle proprietà che può causare una risoluzione DI contorta più in basso. Ad esempio, ho lavorato su un gestore di servizi Windows con oltre 1.6k righe di codice a che fare con Func e dopo averlo fatto il modo consigliato di DI l'ho ridotto a 0,2k righe. OK-Le righe di codice non significano niente .. tranne che è più facile da leggere e resettare ora ...
Piotr Kula

79

Un'altra opzione è utilizzare il metodo di estensione GetServices da Microsoft.Extensions.DependencyInjection.

Registra i tuoi servizi come:

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

Quindi risolvi con un po 'di Linq:

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

o

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

(supponendo che IServiceabbia una proprietà stringa chiamata "Nome")

Assicurati di averlo using Microsoft.Extensions.DependencyInjection;

Aggiornare

Fonte AspNet 2.1: GetServices


6
Non sono sicuro, ma penso che non sia deterministico. Qualsiasi risultato che ottieni oggi potrebbe cambiare domani, non sembra una buona pratica.
rnrneverdies,

4
vota il link per GetServices, che mi ha mostrato che puoi richiedere un elenco di servizi richiedendo un servizio dipendenteIEnumerable<IService>
johnny 5

20
serviceProvider.GetServices <IService> () crea un'istanza di ServiceA, ServiceB e ServiceC. Vorresti chiamare il costruttore di un solo servizio, quello di cui hai effettivamente bisogno. Questo è un grosso problema se le implementazioni non sono leggere o se hai molte implementazioni di IService (ad esempio, hai implementazioni generate automaticamente di IRepository per ciascun modello).
Uros,

6
Sono d'accordo con @Uros. Questa non è una buona soluzione. Immagina cosa succede se registri 10 implementazioni di IService e l'istanza di cui hai effettivamente bisogno è l'ultima. In questo caso, 9 istanze vengono effettivamente create da DI, che non vengono mai utilizzate.
Thom

4
Cattiva idea: più istanze inutilizzate, anti pattern localizzatore di servizio e accoppiamento diretto all'implementazione effettiva (tipo di <ServizioA>).
Rico Suter,

20

Non è supportato da Microsoft.Extensions.DependencyInjection.

Ma puoi collegare un altro meccanismo di iniezione di dipendenza, come StructureMap Vedi la sua Home page ed è il Progetto GitHub .

Non è affatto difficile:

  1. Aggiungi una dipendenza a StructureMap nel tuo project.json:

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
  2. Inseriscilo nella pipeline ASP.NET all'interno ConfigureServicese registra le tue classi (vedi documenti)

    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. Quindi, per ottenere un'istanza denominata, dovrai richiedere il IContainer

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

Questo è tutto.

Per l'esempio da compilare, è necessario

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

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

        public string Name {get; set; }
    }

Ho provato questo approccio, ma ottengo errori di runtime sul mio controller perché IContainer non si trova nei piani di compilazione. C'è qualcosa che devo fare per richiedere l'iniezione automatica di IContainer?
Mohrtan,

A proposito, sto usando StructureMap.Micorosoft.DependencyInjection 1.3.0.
Mohrtan,

Restituisci il nuovo contenitore in ConfigureServices?
Gerardo Grignoli,

Sto restituendo IServiceProviderInstance del nuovo contenitore come indicato nel passaggio 2 sopra. L'ho copiato esattamente cambiandolo solo per i miei tipi. Questa è una buona soluzione e funziona perfettamente. L'unico inconveniente è che non riesco a usare un contenitore iniettato e sto ricorrendo a un contenitore statico, cosa che non voglio fare.
Mohrtan,

1
Funziona per me grazie GerardoGrignoli. @mohrtan il codice di esempio è qui se stai ancora esaminando questo. github.com/Yawarmurtaza/AspNetCoreStructureMap
Yawar Murtaza


13

Ho affrontato lo stesso problema e voglio condividere come l'ho risolto e perché.

Come hai detto, ci sono due problemi. Il primo:

In Asp.Net Core come posso registrare questi servizi e risolverli in fase di esecuzione in base a una chiave?

Quindi quali opzioni abbiamo? La gente suggerisce due:

  • Usa una fabbrica personalizzata (come _myFactory.GetServiceByKey(key))

  • Usa un altro motore DI (come _unityContainer.Resolve<IService>(key))

Il modello Factory è l'unica opzione qui?

In effetti entrambe le opzioni sono fabbriche perché ogni contenitore IoC è anche una fabbrica (altamente configurabile e complicata). E mi sembra che altre opzioni siano anche variazioni del modello Factory.

Quindi quale opzione è migliore allora? Qui sono d'accordo con @Sock che mi ha suggerito di utilizzare la fabbrica personalizzata, ed è per questo.

In primo luogo, cerco sempre di evitare di aggiungere nuove dipendenze quando non sono realmente necessarie. Quindi sono d'accordo con te in questo punto. Inoltre, l'uso di due framework DI è peggiore della creazione di astrazioni personalizzate di fabbrica. Nel secondo caso devi aggiungere una nuova dipendenza dal pacchetto (come Unity) ma a seconda di una nuova interfaccia di fabbrica è meno male qui. L'idea principale di ASP.NET Core DI, credo, è la semplicità. Mantiene un set minimo di funzionalità seguendo il principio KISS . Se hai bisogno di alcune funzionalità extra, fai da te o usa un Plungin corrispondente che implementa la funzione desiderata (Open Closed Principle).

In secondo luogo, spesso è necessario iniettare molte dipendenze nominate per singolo servizio. In caso di Unity potresti dover specificare i nomi per i parametri del costruttore (usando InjectionConstructor). Questa registrazione utilizza la riflessione e alcune logiche intelligenti per indovinare argomenti per il costruttore. Ciò può anche portare a errori di runtime se la registrazione non corrisponde agli argomenti del costruttore. D'altra parte, quando si utilizza la propria fabbrica, si ha il pieno controllo di come fornire i parametri del costruttore. È più leggibile ed è stato risolto in fase di compilazione. Principio KISS di nuovo.

Il secondo problema:

Come può _serviceProvider.GetService () iniettare la stringa di connessione appropriata?

Innanzitutto, sono d'accordo con te sul fatto che dipendere da cose nuove come IOptions(e quindi dal pacchetto Microsoft.Extensions.Options.ConfigurationExtensions) non sia una buona idea. Ho visto alcune discussioni su IOptionsdove c'erano opinioni diverse sul suo beneficio. Ancora una volta, cerco di evitare di aggiungere nuove dipendenze quando non sono realmente necessarie. È davvero necessario? Penso che nessuno. Altrimenti ogni implementazione dovrebbe dipendere da essa senza alcuna chiara necessità proveniente da quella implementazione (per me sembra una violazione dell'ISP, dove sono d'accordo anche con te). Questo vale anche per la dipendenza dalla fabbrica, ma in questo caso può essere evitato.

ASP.NET Core DI offre un ottimo sovraccarico a tale scopo:

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

Ciao, scusami per la mia stupida domanda, ma sto parlando di Microsoft.Extensions.DependencyInjection ... pensi che crei 3 interfacce che estendono Iservice come "interfaccia pubblica IServiceA: IService" e che "servizio pubblico di classe A: IServiceA" ... potrebbe essere un'opzione di buona pratica?
Emiliano Magliocca,

1
@ emiliano-magliocca In generale, non dovresti dipendere dalle interfacce che non usi (ISP), IServiceAnel tuo caso. Dal momento che stai usando IServicesolo metodi , dovresti avere IServicesolo dipendenza .
neleus,

1
@ cagatay-kalan In caso di domanda di OP, può facilmente raggiungere il suo obiettivo con ASP.NET Core DI. Non sono necessari altri framework DI.
neleus,

1
@EmilianoMagliocca Può essere facilmente risolto in questo modo: services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));per la prima classe e services.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));per la seconda.
neleus,

1
@EmilianoMagliocca nel mio esempio sia 'MyFirstClass' che 'MySecondClass' hanno lo stesso parametro ctor di tipo interfaccia che implementano sia Escpos che Usbpos. Quindi il codice sopra indica solo al contenitore IoC come installare "MyFirstClass" e "MySecondClass". Niente di più. Inoltre, potrebbe essere necessario mappare alcune altre interfacce su "MyFirstClass" e "MySecondClass". Dipende dalle tue esigenze e non l'ho coperto nel mio esempio.
neleus,

13

Ho semplicemente iniettato un IEnumerable

ConfigureServices in Startup.cs

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

Cartella servizi

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

Nel metodo DoSomething () del controller è possibile utilizzare typeof per risolvere il servizio desiderato: var service = _services.FirstOrDefault (t => t.GetType () == typeof (ServiceA));
Ciaran Bruen,

Ho letteralmente provato tutto, e questa è l'unica soluzione che ha funzionato per me. Grazie!
Skatz1990,

@ Skatz1990 Prova la soluzione che ho creato di seguito in un altro post. Penso che sia più pulito e più semplice da usare.
T Brown

12

La maggior parte delle risposte qui violano il principio di responsabilità singola (una classe di servizio non dovrebbe risolvere le dipendenze stesse) e / o utilizzare l'anti-schema di localizzazione del servizio.

Un'altra opzione per evitare questi problemi è:

  • utilizzare un ulteriore parametro di tipo generico sull'interfaccia o una nuova interfaccia che implementa l'interfaccia non generica,
  • implementare una classe adattatore / intercettore per aggiungere il tipo di marcatore e quindi
  • usa il tipo generico come "nome"

Ho scritto un articolo con maggiori dettagli: Iniezione delle dipendenze in .NET: un modo per aggirare le registrazioni mancanti


in che modo la risposta accettata viola il principio della responsabilità singola?
LP13

Vedi i commenti di stackoverflow.com/a/52066039/876814 e anche nella risposta accettata il servizio è risolto in modo pigro, cioè sai solo se fallisce in fase di esecuzione e non c'è modo di controllarlo staticamente all'avvio dopo la creazione del contenitore (simile a la risposta nel commento). SRP perché il servizio non è responsabile solo della sua logica aziendale, ma anche della risoluzione delle dipendenze
Rico Suter,

@RicoSuter Mi piace molto la soluzione nel tuo blog, ma sono confuso dal tuo DI all'interno della classe Startup. In particolare, non capisco la riga MessagePublisher ("MyOrderCreatedQueue") poiché non vedo un costruttore con quella firma. services.AddSingleton <IMessagePublisher <OrderCreatedMessage>> (nuovo MessagePublisher <OrderCreatedMessage> (nuovo MessagePublisher ("MyOrderCreatedQueue")));
Lee Z,

Grazie, aggiornato l'articolo e utilizzo MyMessagePublisher come implementazione di esempio di IMessagePublisher
Rico Suter il

7

Un po 'tardi a questa festa, ma ecco la mia soluzione: ...

Startup.cs o Program.cs se gestore generico ...

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

IMyInterface of T Interface Setup

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

Implementazioni concrete di IMyInterface di T

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

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

Spero che se c'è qualche problema nel farlo in questo modo, qualcuno indicherà gentilmente perché questo è il modo sbagliato di farlo.


2
IMyInterface<CustomerSavedConsumer>e IMyInterface<ManagerSavedConsumer>sono diversi tipi di servizio - questo non risponde affatto alla domanda dei PO.
Richard Hauer,

2
L'OP voleva un modo per registrare più implementazioni della stessa interfaccia nel core di Asp.net. Se non l'ho fatto, ti preghiamo di spiegare come (esattamente).
Gray,

1
Mentre hai ragione, questo modello consente l'effetto desiderato dall'operazione. Almeno quando stavo cercando di farlo da solo, mi sono imbattuto in questo post e la mia soluzione ha funzionato meglio per la mia situazione.
Gray,

1
Mi aspetto che il problema sia più che la registrazione di più implementazioni per una singola interfaccia (utilizzando MS DI) non consente al contenitore di distinguere un'implementazione da un'altra. In altri DI puoi digitarli in modo che il contenitore sappia quale scegliere. In MS si deve utilizzare un delegato e scegliere manualmente. La tua soluzione non affronta questo scenario poiché le tue interfacce sono diverse, quindi il contenitore non ha problemi a scegliere l'implementazione corretta. Mentre il tuo campione ovviamente funziona, non è una soluzione al problema come indicato.
Richard Hauer,

3
@Gray Anche se il tuo post ha ricevuto una cattiva stampa, ti ringrazio per aver proposto questa soluzione. Offre ai lettori un'altra opzione per superare i limiti dei core .net DI. Sebbene possa non rispondere direttamente alla domanda dei PO, fornisce una soluzione alternativa perfetta, di cosa si tratta SO, giusto?
Neil Watson,

5

Apparentemente, puoi semplicemente iniettare IEnumerable della tua interfaccia di servizio! E poi trova l'istanza che desideri utilizzando LINQ.

Il mio esempio è per il servizio AWS SNS ma puoi fare lo stesso per qualsiasi servizio iniettato.

Avviare

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

Ora puoi ottenere il servizio SNS per la regione desiderata nel tuo servizio o controller personalizzato

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

     .........
}

5

Un approccio di fabbrica è certamente praticabile. Un altro approccio consiste nell'utilizzare l'ereditarietà per creare singole interfacce che ereditano da IService, implementare le interfacce ereditate nelle implementazioni di IService e registrare le interfacce ereditate anziché la base. Se l'aggiunta di una gerarchia ereditaria o di fabbriche è il modello "giusto" tutto dipende da chi parli. Spesso devo usare questo modello quando ho a che fare con più provider di database nella stessa applicazione che utilizza un generico, comeIRepository<T> esempio la base per l'accesso ai dati.

Interfacce e implementazioni di esempio:

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

Contenitore:

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

5

Necromancing.
Penso che le persone qui stiano reinventando la ruota - e male, se così posso dire ...
Se vuoi registrare un componente per chiave, usa un dizionario:

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

E quindi registra il dizionario con la raccolta servizi:

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

se non si desidera ottenere il dizionario e accedervi con la chiave, è possibile nascondere il dizionario aggiungendo un ulteriore metodo di ricerca delle chiavi alla raccolta di servizi:
(l'uso del delegato / chiusura dovrebbe dare la possibilità a un potenziale manutentore di capire cosa sta succedendo - la notazione a freccia è un po 'enigmatica)

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

Ora puoi accedere ai tuoi tipi con entrambi

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

o

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

Come possiamo vedere, il primo è completamente superfluo, perché puoi anche fare esattamente questo con un dizionario, senza richiedere chiusure e AddTransient (e se usi VB, nemmeno le parentesi graffe saranno diverse):

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

(più semplice è meglio - potresti voler usarlo come metodo di estensione però)

Naturalmente, se non ti piace il dizionario, puoi anche equipaggiare la tua interfaccia con una proprietà Name(o qualsiasi altra cosa) e cercarla con il tasto:

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

Ma ciò richiede la modifica dell'interfaccia per adattarla alla proprietà e il looping tra molti elementi dovrebbe essere molto più lento di una ricerca di array associativo (dizionario).
È bello sapere che può essere fatto senza dicionario, però.

Questi sono solo i miei $ 0,05


Se il servizio è stato IDisposeimplementato, chi è responsabile per lo smaltimento del servizio? Hai registrato il dizionario comeSingleton
LP13

@ LP13: puoi anche registrare un dizionario con un delegato come valore, quindi puoi registrarlo in itransient e creare una nuova istanza, ad es. GetRequiredService <T> () ["logDB"] ()
Stefan Steiger

5

dal mio post sopra, sono passato a una classe Factory generica

uso

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

Implementazione

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

Estensione

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

Potete fornire l'estensione del metodo .AddFactory ()?
sviluppatore

Siamo spiacenti, ho appena visto questo ... aggiunto
T Brown

3

Mentre sembra che @Miguel A. Arilla l'abbia sottolineato chiaramente e io abbia votato per lui, ho creato in cima alla sua utile soluzione un'altra soluzione che sembra pulita ma richiede molto più lavoro.

Dipende sicuramente dalla soluzione sopra. Quindi fondamentalmente ho creato qualcosa di simile Func<string, IService>>e l'ho chiamato IServiceAccessorcome interfaccia e quindi ho dovuto aggiungere alcune estensioni in IServiceCollectionquanto tale:

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

Il servizio Accessor è simile a:

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

    }

Il risultato finale, sarai in grado di registrare i servizi con nomi o istanze denominate come facevamo con altri container ... ad esempio:

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

Per ora è abbastanza, ma per completare il tuo lavoro, è meglio aggiungere più metodi di estensione in quanto puoi coprire tutti i tipi di registrazioni seguendo lo stesso approccio.

C'era un altro post su StackOverflow, ma non riesco a trovarlo, in cui il poster ha spiegato in dettaglio perché questa funzione non è supportata e come aggirare, sostanzialmente simile a quanto affermato da @Miguel. È stato un bel post anche se non sono d'accordo con ogni punto perché penso che ci siano situazioni in cui hai davvero bisogno di istanze denominate. Pubblicherò quel link qui una volta che lo troverò di nuovo.

È un dato di fatto, non è necessario passare quel Selettore o Accessore:

Sto usando il seguente codice nel mio progetto e finora ha funzionato bene.

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

1
Ciò ha aiutato a risolvere il mio problema in cui stavo perdendo la registrazione dei tipi nell'accessorio di servizio. Il trucco era rimuovere tutti i binding per l'accessor di servizio e quindi aggiungerlo di nuovo!
Umar Farooq Khawaja,

3

Per questo ho creato una libreria che implementa alcune belle funzionalità. Il codice è disponibile su GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/

L'utilizzo è semplice:

  1. Aggiungi il pacchetto nuget Dazinator.Extensions.DependencyInjection al tuo progetto.
  2. Aggiungi le tue registrazioni al servizio denominato.
    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.

    });

Nell'esempio sopra, si noti che per ciascuna registrazione denominata, si specifica anche la durata o Singleton, Scoped o Transient.

Puoi risolvere i servizi in uno dei due modi seguenti, a seconda che tu abbia dimestichezza con la dipendenza dei tuoi servizi da questo pacchetto di:

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

o

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

Ho appositamente progettato questa libreria per funzionare bene con Microsoft.Extensions.DependencyInjection - ad esempio:

  1. Al momento della registrazione servizi denominati, di tutti i tipi che si registra può avere costruttori con parametri - perché saranno saziati tramite DI, nello stesso modo in cui AddTransient<>, AddScoped<>e AddSingleton<>metodi di lavoro normalmente.

  2. Per i servizi con nome transitorio e con ambito, il registro crea un ObjectFactorymodo per poter attivare nuove istanze del tipo molto rapidamente quando necessario. Questo è molto più veloce di altri approcci ed è in linea con il modo in cui Microsoft.Extensions.DependencyInjection fa le cose.


2

La mia soluzione per quello che vale ... considerato il passaggio a Castle Windsor come non posso dire che mi è piaciuta nessuna delle soluzioni sopra. Scusate!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

Crea le tue varie implementazioni

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

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

Registrazione

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

Uso del costruttore e dell'istanza ...

public class Whatever
{
   private IStage ClassA { get; }

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

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

1

Estensione della soluzione di @rnrneverdies. Invece di ToString (), è possibile utilizzare anche le seguenti opzioni: 1) Con l'implementazione di proprietà comuni, 2) Un servizio di servizi suggerito da @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....
}

1

Dopo aver letto le risposte qui e gli articoli altrove sono stato in grado di farlo funzionare senza stringhe. Quando si hanno più implementazioni della stessa interfaccia, DI li aggiungerà a una raccolta, quindi è possibile recuperare la versione desiderata dalla raccolta utilizzando 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();
    }
}

Sconfigge lo scopo di IoC. Potresti anche scrivere:var serviceA = new ServiceA();
James Curran il

2
@JamesCurran non se ServiceA ha dipendenze o se si desidera testare l'unità della classe.
Jorn.Beyers,

0

Mentre l'implementazione immediata non lo offre, ecco un progetto di esempio che ti consente di registrare istanze denominate, quindi iniettare INamedServiceFactory nel tuo codice ed estrarre istanze per nome. A differenza di altre soluzioni facory qui, ti permetterà di registrare più istanze della stessa implementazione ma configurate in modo diverso

https://github.com/macsux/DotNetDINamedInstances


0

Che ne dici di un servizio per i servizi?

Se avessimo un'interfaccia INamedService (con la proprietà .Name), potremmo scrivere un'estensione IServiceCollection per .GetService (nome stringa), dove l'estensione prenderebbe quel parametro stringa e farebbe un .GetServices () su se stesso e in ogni restituito istanza, trova l'istanza il cui INamedService.Name corrisponde al nome specificato.

Come questo:

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

Pertanto, il tuo IMyService deve implementare INamedService, ma otterrai la risoluzione basata sulle chiavi che desideri, giusto?

Ad essere onesti, dover persino avere questa interfaccia INamedService sembra brutta, ma se volessi andare oltre e rendere le cose più eleganti, allora un [NamedServiceAttribute ("A")] sull'implementazione / classe potrebbe essere trovato dal codice in questo estensione, e funzionerebbe altrettanto bene. Per essere ancora più equi, Reflection è lento, quindi un'ottimizzazione potrebbe essere in ordine, ma onestamente è qualcosa che il motore DI avrebbe dovuto aiutare. Velocità e semplicità contribuiscono in modo determinante al TCO.

Tutto sommato, non c'è bisogno di una fabbrica esplicita, perché "trovare un servizio denominato" è un concetto così riutilizzabile e le classi di fabbrica non si adattano come soluzione. E un Func <> sembra a posto, ma un blocco di interruttori è così bleh , e di nuovo, scriverai Funcs tutte le volte che scriverai Fabbriche. Inizia in modo semplice, riutilizzabile, con meno codice e, se risulta che non lo fai per te, diventa complesso.


2
Questo è chiamato lo schema di localizzazione del servizio che è e non è in genere la migliore strada da percorrere a meno che tu non debba assolutamente farlo
Joe Phillips,

@JoePhillips Hai qualche input sul perché non sia una buona soluzione? ne amo l'eleganza. L'unico aspetto negativo che mi viene in mente è che creo un'istanza di tutti loro ogni volta che ne ottieni uno.
Peter,

2
@Peter Il motivo principale è perché è molto difficile lavorare con. Se stai passando un oggetto serviceLocator in una classe, non è affatto ovvio quali dipendenze utilizzi quella classe poiché le sta ottenendo tutte da un oggetto magico "dio". Immagina di dover trovare riferimenti del tipo che desideri modificare. Questa abilità praticamente scompare quando ottieni tutto attraverso un oggetto localizzatore di servizio. L'iniezione del costruttore è molto più chiara e affidabile
Joe Phillips,

Non so. L'ovvietà non è un aspetto negativo per me ... perché se mi tenessi a tenere traccia di come i miei componenti sfruttano le loro dipendenze, avrei dei test unitari per quello ... test che non si riferiscono solo a ciascuna dipendenza, ma ci aiutano a capire COME è necessaria ogni dipendenza. In che altro modo ne sarai consapevole leggendo i costruttori?!?
Craig Brunetti,

0

Ho riscontrato lo stesso problema e ho lavorato con una semplice estensione per consentire i servizi con nome. Potete trovare qui:

Ti consente di aggiungere tutti i servizi (nominati) che desideri in questo modo:

 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 libreria consente inoltre di implementare facilmente un "modello di fabbrica" ​​come questo:

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

Spero che sia d'aiuto


0

Ho creato la mia estensione sull'estensione IServiceCollectionusata 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;
    }

ServiceCollectionTypeMapperè un'istanza singleton che le mappe IService> NameOfService> Implementationdove un'interfaccia potrebbe avere molte implementazioni con nomi diversi, questo permette di registrare i tipi di quanto possiamo risolvere quando il bisogno wee ed è un approccio diverso rispetto ai servizi multipli volontà di scegliere ciò che vogliamo.

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

Per registrare un nuovo servizio:

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

Per risolvere il servizio abbiamo bisogno di un'estensione IServiceProvidercome questa.

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

Quando risolvi:

serviceProvider.GetService<IWithdrawalHandler>(serviceName);

Ricorda che serviceProvider può essere iniettato all'interno di un costruttore nella nostra applicazione come IServiceProvider .

Spero che aiuti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.