Como registrar várias implementações da mesma interface no Asp.Net Core?

240

Eu tenho serviços derivados da mesma interface.

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

Normalmente, outros contêineres IoC como Unitypermitem registrar implementações concretas por algumas Keyque as distinguem.

No ASP.NET Core, como faço para registrar esses serviços e resolvê-los em tempo de execução com base em alguma chave?

Não vejo nenhum Addmétodo de serviço que aceite um parâmetro keyou name, que normalmente seria usado para distinguir a implementação 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?
       }
    }

O padrão de fábrica é a única opção aqui?

Atualização1 Analisei
o artigo aqui que mostra como usar o padrão de fábrica para obter instâncias de serviço quando temos várias implementações concretas. No entanto, ainda não é uma solução completa. Quando ligo para o_serviceProvider.GetService() método, não consigo injetar dados no construtor.

Por exemplo, considere isso:

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

Como _serviceProvider.GetService()injetar a string de conexão apropriada? No Unity, ou em qualquer outra biblioteca de IoC, podemos fazer isso no registro de tipo. No entanto, posso usar a IOption que exigirá que eu injete todas as configurações. Não consigo injetar uma sequência de conexão específica no serviço.

Observe também que estou tentando evitar o uso de outros contêineres (incluindo o Unity), porque também tenho que registrar todo o resto (por exemplo, controladores) no novo contêiner.

Além disso, o uso do padrão de fábrica para criar instâncias de serviço é contra o DIP, pois aumenta o número de dependências que um cliente possui. detalhes aqui .

Então, acho que o DI padrão no ASP.NET Core está faltando duas coisas:

  1. A capacidade de registrar instâncias usando uma chave
  2. A capacidade de injetar dados estáticos nos construtores durante o registro
LP13
fonte
4
Possível duplicado de injeção de dependência resolver pelo nome
caglin adem
2
Há, finalmente, uma extensão no NuGet para inscrições baseados em nome, espero que possa ajudá
Neleus
Olá, desculpe pela minha pergunta estúpida, mas eu sou novo no Microsoft.Extensions.DependencyInjection ... você acha que cria 3 interfaces vazias que estendem o Iservice como "interface pública IServiceA: IService" e "classe pública ServiceA: IServiceA "... poderia ser uma opção de boas práticas?
Emiliano Magliocca
este artigo tem alguma utilidade? stevejgordon.co.uk/...
Mike B
Pode Update1ser movido para uma pergunta diferente, pois injetar coisas nos construtores é muito diferente de descobrir qual objeto construir
Neil

Respostas:

246

Eu fiz uma solução simples usando Func quando me encontrei nessa situação.

Em primeiro lugar, declare um delegado compartilhado:

public delegate IService ServiceResolver(string key);

Em seguida, no seu Startup.cs, configure os vários registros concretos e um mapeamento manual desses tipos:

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 use-o de qualquer classe registrada no DI:

public class Consumer
{
    private readonly IService _aService;

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

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

Lembre-se de que, neste exemplo, a chave da resolução é uma string, por uma questão de simplicidade e porque o OP estava solicitando esse caso em particular.

Mas você pode usar qualquer tipo de resolução personalizado como chave, pois geralmente não deseja que uma grande opção de n-case apague seu código. Depende de como o seu aplicativo é dimensionado.

Miguel A. Arilla
fonte
1
@MatthewStevenMonkan atualizou minha resposta com um exemplo
Miguel A. Arilla
2
Usar um padrão de fábrica como este é o melhor caminho a percorrer. Obrigado por compartilhar!
Sergey Akopov
2
+1 Muito arrumado e limpo, porque quando usamos outro contêiner, precisamos incluir o pacote sempre que precisamos resolver dependências, por exemplo. ILifetimeScope no AutoFac.
Anupam Singh
1
@AnupamSingh Na minha opinião, a maioria dos aplicativos de pequeno a médio porte executando no .NET Core não precisa de nenhuma estrutura de DI, apenas adiciona complexidade e dependências indesejadas, a beleza e a simplicidade da DI incorporada são mais do que suficientes e podem também pode ser estendido com facilidade.
Miguel A. Arilla
7
Explicação de down vote - É muito interessante, mas atualmente estou refatorando uma base de código massiva para remover toda essa mágica do Func que alguém fez alguns anos atrás (antes da revolução do MS DI). O problema disso é que aumenta drasticamente a complexidade da conascência nas propriedades que pode causar resolução de DI complicada mais abaixo na linha. Por exemplo, eu trabalhei em um manipulador de serviços do Windows com mais de 1,6 mil linhas de código relacionadas ao Func e, depois de fazer isso da maneira recomendada do DI, reduzi-o para 0,2 mil linhas. OK-Linhas de código não significam nada .. exceto a sua mais fácil de ler e resuse agora ...
Piotr Kula
79

Outra opção é usar o método GetServicesde extensão deMicrosoft.Extensions.DependencyInjection .

Registre seus serviços como:

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

Então resolva com um pouco 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"));

(supondo que IServicetenha uma propriedade de sequência chamada "Nome")

Certifique-se de ter using Microsoft.Extensions.DependencyInjection;

Atualizar

Fonte AspNet 2.1: GetServices

rnrneverdies
fonte
6
Não tenho certeza, mas acho que não é determinístico. Quaisquer resultados obtidos hoje podem mudar amanhã, não parece uma boa prática.
Rnrneverdies
4
voto a favor no link para GetServices, que me mostrou que você pode solicitar uma lista de serviços como um serviço dependente, solicitandoIEnumerable<IService>
johnny 5 /
20
serviceProvider.GetServices <IService> () instancia cada ServiceA, ServiceB e ServiceC. Você gostaria de chamar o construtor de apenas um serviço - o que você realmente precisa. Esse é um grande problema se as implementações não forem leves ou você tiver muitas implementações do IService (por exemplo, você tem implementações geradas automaticamente do IRepository para cada modelo).
Uros
6
Eu concordo com @Uros. Esta não é uma boa solução. Imagine o que acontece se você registrar 10 implementações do IService e a instância que você realmente precisa é a última. Nesse caso, 9 instâncias são realmente criadas pelo DI, que nunca são usadas.
thomai 5/09/19
4
Má ideia: várias instâncias não utilizadas, antipadrão do localizador de serviço e acoplamento direto à implementação real (tipo de <ServiçoA>).
Rico Suter
20

Não é suportado por Microsoft.Extensions.DependencyInjection .

Mas você pode conectar outro mecanismo de injeção de dependência, como StructureMap Veja a página inicial e o projeto GitHub .

Não é nada difícil:

  1. Adicione uma dependência ao StructureMap no seu project.json:

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
  2. Injete-o no pipeline do ASP.NET dentro ConfigureServicese registre suas classes (consulte a documentação)

    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. Em seguida, para obter uma instância nomeada, você precisará solicitar o IContainer

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

É isso aí.

Para o exemplo construir, você precisa

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

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

        public string Name {get; set; }
    }
Gerardo Grignoli
fonte
Eu tentei essa abordagem, mas recebo erros de tempo de execução no meu controlador porque o IContainer não é encontrado nos planos de compilação. Há algo que eu precise para exigir que o IContainer seja injetado automaticamente?
mohrtan
BTW, eu estou usando StructureMap.Micorosoft.DependencyInjection 1.3.0.
mohrtan
Você está retornando o novo contêiner no ConfigureServices?
Gerardo Grignoli
Estou retornando o IServiceProviderInstance do novo contêiner, conforme indicado na etapa 2 acima. Copiei exatamente isso, apenas alterando-o para os meus tipos. Esta é uma boa solução e está funcionando perfeitamente. A única desvantagem é que não consigo usar um contêiner injetado e estou recorrendo a um contêiner estático, o que não quero fazer.
mohrtan
1
Isso funciona para mim, graças a GerardoGrignoli. @mohrtan o código de exemplo está aqui, se você ainda está investigando isso. github.com/Yawarmurtaza/AspNetCoreStructureMap
Yawar Murtaza
13

Você está correto, o contêiner interno do ASP.NET Core não tem o conceito de registrar vários serviços e recuperar um específico, como você sugere, uma fábrica é a única solução real nesse caso.

Como alternativa, você pode mudar para um contêiner de terceiros, como o Unity ou o StructureMap, que fornece a solução de que você precisa (documentado aqui: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- o contêiner de serviços padrão ).

Meia
fonte
13

Eu enfrentei o mesmo problema e quero compartilhar como o resolvi e por quê.

Como você mencionou, existem dois problemas. O primeiro:

No Asp.Net Core, como faço para registrar esses serviços e resolvê-los em tempo de execução com base em alguma chave?

Então, quais opções temos? O pessoal sugere dois:

  • Use uma fábrica personalizada (como _myFactory.GetServiceByKey(key))

  • Use outro mecanismo de DI (como _unityContainer.Resolve<IService>(key))

O padrão de fábrica é a única opção aqui?

De fato, ambas as opções são fábricas porque cada container IoC também é uma fábrica (embora altamente configurável e complicada). E parece-me que outras opções também são variações do padrão de fábrica.

Então, qual opção é melhor então? Concordo aqui com a @Sock, que sugeriu o uso de fábrica personalizada, e é por isso.

Primeiro, sempre tento evitar adicionar novas dependências quando elas não são realmente necessárias. Então, eu concordo com você neste ponto. Além disso, o uso de duas estruturas de DI é pior do que criar abstração de fábrica personalizada. No segundo caso, você precisa adicionar uma nova dependência de pacote (como o Unity), mas dependendo de uma nova interface de fábrica é menos ruim aqui. A principal idéia do ASP.NET Core DI, acredito, é a simplicidade. Mantém um conjunto mínimo de recursos seguindo o princípio do KISS . Se você precisar de algum recurso extra, faça você mesmo ou use um Plungin correspondente que implemente o recurso desejado (Princípio Aberto Fechado).

Em segundo lugar, geralmente precisamos injetar muitas dependências nomeadas para um único serviço. No caso do Unity, você pode precisar especificar nomes para os parâmetros do construtor (usando InjectionConstructor). Esse registro usa reflexão e alguma lógica inteligente para adivinhar argumentos para o construtor. Isso também pode levar a erros de tempo de execução se o registro não corresponder aos argumentos do construtor. Por outro lado, ao usar sua própria fábrica, você tem controle total de como fornecer os parâmetros do construtor. É mais legível e é resolvido em tempo de compilação. Princípio KISS novamente.

O segundo problema:

Como _serviceProvider.GetService () injeta a seqüência de conexão apropriada?

Primeiro, concordo com você que, dependendo de coisas novas como IOptions(e, portanto, do pacote Microsoft.Extensions.Options.ConfigurationExtensions), não é uma boa ideia. Eu já vi algumas discussões sobre IOptionsonde havia opiniões diferentes sobre seu benefício. Mais uma vez, tento evitar adicionar novas dependências quando elas não são realmente necessárias. É realmente necessário? Eu acho que não. Caso contrário, cada implementação teria que depender dela, sem necessidade clara dessa implementação (para mim, parece uma violação do ISP, onde também concordo com você). Isso também é verdade quanto à dependência da fábrica, mas, neste caso, pode ser evitado.

O ASP.NET Core DI fornece uma sobrecarga muito agradável para esse fim:

var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
             s => new MyFactoryImpl(
                 mongoConnection, efConnection, otherConnection, 
                 s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));
neleus
fonte
Olá, desculpe pela minha pergunta estúpida, mas eu sou novo no Microsoft.Extensions.DependencyInjection ... você acha que cria 3 interfaces que estendem o Iservice como "interface pública IServiceA: IService" e "public class ServiceA: IServiceA" ... poderia ser uma opção de boas práticas?
Emiliano Magliocca
1
@ emiliano-magliocca Em geral, você não deve depender de interfaces que você não usa (ISP), IServiceAno seu caso. Como você está usando métodos IServiceapenas, você deve ter IServiceapenas dependência .
Neleus
1
@ cagatay-kalan No caso da pergunta do OP, ele pode facilmente atingir seu objetivo com o ASP.NET Core DI. Não há necessidade de outras estruturas de DI.
Neleus
1
@EmilianoMagliocca Pode ser facilmente resolvido desta maneira: services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));para a primeira classe e services.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));para a segunda.
neleus 19/01/19
1
@EmilianoMagliocca no meu exemplo, 'MyFirstClass' e 'MySecondClass' têm o mesmo parâmetro ctor do tipo de interface que o Escpos e o Usbpos implementam. Portanto, o código acima instrui apenas o contêiner de IoC como instanciar 'MyFirstClass' e 'MySecondClass'. Nada mais. Além disso, você pode precisar mapear outras interfaces para 'MyFirstClass' e 'MySecondClass'. Depende das suas necessidades e não o cobri no meu exemplo.
neleus 23/01/19
13

Eu simplesmente injeto um IEnumerable

ConfigureServices em Startup.cs

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

Pasta Serviços

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 Marrom
fonte
No método DoSomething () do Controller, você pode usar typeof para resolver o serviço que deseja: var service = _services.FirstOrDefault (t => t.GetType () == typeof (ServiceA));
Ciaran Bruen
Eu literalmente tentei de tudo, e esta é a única solução que funcionou para mim. Obrigado!
Skatz1990 29/01
@ Skatz1990 Experimente a solução que criei abaixo em outro post. Eu acho que é mais limpo e mais simples de usar.
T Brown
12

A maioria das respostas aqui viola o princípio da responsabilidade única (uma classe de serviço não deve resolver as dependências) e / ou usa o antipadrão do localizador de serviços.

Outra opção para evitar esses problemas é:

  • use um parâmetro de tipo genérico adicional na interface ou uma nova interface que implemente a interface não genérica,
  • implementar uma classe de adaptador / interceptador para adicionar o tipo de marcador e, em seguida,
  • use o tipo genérico como "nome"

Escrevi um artigo com mais detalhes: Injeção de Dependências no .NET: Uma maneira de solucionar os registros nomeados ausentes

Rico Suter
fonte
como a resposta aceita viola o princípio da responsabilidade única?
LP13 6/08/19
Consulte os comentários de stackoverflow.com/a/52066039/876814 e também na resposta aceita que o serviço seja resolvido preguiçosamente, ou seja, você só sabe se ele falha no tempo de execução e não há como verificar isso estaticamente na inicialização após a construção do contêiner (semelhante a a resposta no comentário). SRP porque o serviço não é apenas responsável por sua lógica de negócios, mas também pela resolução de dependências
Rico Suter
@RicoSuter Eu realmente gosto da solução em seu blog, mas estou confuso com seu DI dentro da classe Startup. Especificamente, não entendo a linha MessagePublisher ("MyOrderCreatedQueue"), pois não vejo um construtor com essa assinatura. services.AddSingleton <IMessagePublisher <OrderCreatedMessage>> (novo MessagePublisher <OrderCreatedMessage> (novo MessagePublisher ("MyOrderCreatedQueue")));
Lee Z
Obrigado, atualizou o artigo e use o MyMessagePublisher como uma implementação de exemplo do IMessagePublisher
Rico Suter
7

Um pouco atrasado para esta festa, mas aqui está a minha solução: ...

Startup.cs ou Program.cs se Generic Handler ...

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

IMyInterface da configuração da interface T

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

Implementações concretas do IMyInterface of T

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

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

Felizmente, se houver algum problema em fazê-lo dessa maneira, alguém indicará por que essa é a maneira errada de fazer isso.

cinzento
fonte
2
IMyInterface<CustomerSavedConsumer>e IMyInterface<ManagerSavedConsumer>são tipos de serviço diferentes - isso não responde à pergunta dos OPs.
Richard Hauer
2
O OP queria uma maneira de registrar várias implementações da mesma interface no núcleo do Asp.net. Se eu não fiz isso, explique como (exatamente).
Grey
1
Enquanto você estiver correto, esse padrão permite o efeito desejado pela operação. Pelo menos quando eu estava tentando fazer isso sozinho, me deparei com este post e minha solução funcionou melhor para a minha situação.
Grey
1
Espero que o problema seja mais que o registro de várias implementações para uma única interface (usando o MS DI) não permita que o contêiner diferencie uma implementação da outra. Em outras DIs, é possível digitá-las para que o contêiner saiba qual escolher. Em MS que você tem de usar um delegado e escolher manualmente. Sua solução não aborda esse cenário, pois suas interfaces são diferentes; portanto, o contêiner não apresenta problemas ao escolher a implementação correta. Embora sua amostra obviamente funcione, não é uma solução para o problema, conforme indicado.
Richard Hauer
3
@ Grey Embora seu post tenha recebido uma má impressão, agradeço por apresentar esta solução. Dá aos leitores outra opção para superar as limitações nos núcleos .net DI. Embora possa não responder diretamente à pergunta dos OPs, fornece uma solução alternativa perfeita, e é disso que se trata o SO, certo?
Neil Watson
5

Aparentemente, você pode simplesmente injetar IEnumerable da sua interface de serviço! E, em seguida, encontre a instância que você deseja usar o LINQ.

Meu exemplo é para o serviço AWS SNS, mas você pode fazer o mesmo para qualquer serviço injetado.

Comece

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

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

Agora você pode obter o serviço SNS da região que deseja em seu serviço ou controlador personalizado

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

     .........
}
ArcadeRenegade
fonte
5

Uma abordagem de fábrica é certamente viável. Outra abordagem é usar a herança para criar interfaces individuais herdadas do IService, implementar as interfaces herdadas nas implementações do IService e registrar as interfaces herdadas em vez da base. Se a adição de uma hierarquia de herança ou fábricas é o padrão "certo", tudo depende de com quem você fala. Costumo usar esse padrão ao lidar com vários provedores de banco de dados no mesmo aplicativo que usa um genérico, comoIRepository<T> , a base para o acesso a dados.

Exemplos de interfaces e implementações:

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

Recipiente:

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

Necromante.
Acho que as pessoas aqui estão reinventando a roda - e muito, se assim posso dizer ...
Se você deseja registrar um componente por chave, basta usar um dicionário:

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 então registre o dicionário com a coleção de serviços:

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

se você não estiver disposto a obter o dicionário e acessá-lo por chave, poderá ocultá-lo adicionando um método de pesquisa de chave adicional à coleção de serviços:
(o uso de delegado / fechamento deve permitir ao mantenedor em potencial a chance de entender o que está acontecendo - a notação de seta é um pouco enigmática)

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

Agora você pode acessar seus tipos com

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

Como podemos ver, o primeiro é completamente supérfluo, porque você também pode fazer exatamente isso com um dicionário, sem a necessidade de fechamentos e AddTransient (e se você usa VB, nem os chavetas serão diferentes):

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

(quanto mais simples, melhor - você pode usá-lo como método de extensão)

Obviamente, se você não gosta do dicionário, também pode equipar sua interface com uma propriedade Name(ou qualquer outra coisa) e procurar por chave:

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

Mas isso requer alteração da interface para acomodar a propriedade, e o loop de vários elementos deve ser muito mais lento que uma pesquisa de dicionário associativo (dicionário).
É bom saber que isso pode ser feito sem dicionário.

Estes são apenas os meus $ 0,05

Stefan Steiger
fonte
Se o serviço foi IDisposeimplementado, quem é responsável pela disposição do serviço? Você registrou o dicionário comoSingleton
LP13
@ LP13: Você também pode registrar o dicionário com um delegado como valor, depois pode registrá-lo no itransient e criar uma nova instância, por exemplo. GetRequiredService <T> () ["logDB"] ()
Stefan Steiger
5

desde o meu post acima, mudei para uma classe de fábrica genérica

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

Implementação

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

Extensão

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 Marrom
fonte
Você pode fornecer a extensão do método .AddFactory ()?
desenvolvedor
Desculpe Só vi isso ... adicionado
T Brown
3

Embora pareça que @Miguel A. Arilla apontou claramente e eu votei nele, criei em cima de sua solução útil outra solução que parece legal, mas requer muito mais trabalho.

Definitivamente depende da solução acima. Então, basicamente, criei algo semelhante Func<string, IService>>e o chamei de IServiceAccessorinterface, e tive que adicionar mais algumas extensões ao seguinte IServiceCollection:

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

O serviço Accessor é semelhante a:

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

    }

O resultado final, você poderá registrar serviços com nomes ou instâncias nomeadas, como costumávamos fazer com outros contêineres ... por exemplo:

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

Isso é o suficiente por enquanto, mas para concluir seu trabalho, é melhor adicionar mais métodos de extensão possível para cobrir todos os tipos de registros, seguindo a mesma abordagem.

Houve outra postagem no stackoverflow, mas não consigo encontrá-la, onde o pôster explicou em detalhes por que esse recurso não é suportado e como contorná-lo, basicamente semelhante ao que o @Miguel afirmou. Foi um post legal, embora eu não concorde com cada ponto, porque acho que há situações em que você realmente precisa de instâncias nomeadas. Vou postar esse link aqui quando o encontrar novamente.

Por uma questão de fato, você não precisa passar nesse Seletor ou Acessador:

Estou usando o seguinte código no meu projeto e funcionou bem até agora.

 /// <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
fonte
1
Isso ajudou a resolver meu problema em que eu estava perdendo o registro de tipos no acessador de serviço. O truque era remover todas as ligações do acessador de serviço e adicioná-lo novamente!
Umar Farooq Khawaja
3

Eu criei uma biblioteca para isso que implementa alguns recursos interessantes. O código pode ser encontrado no GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/

O uso é direto:

  1. Adicione o pacote de nuget Dazinator.Extensions.DependencyInjection ao seu projeto.
  2. Adicione seus registros de Serviço Nomeado.
    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.

    });

No exemplo acima, observe que, para cada registro nomeado, você também está especificando a vida útil ou Singleton, Scoped ou Transient.

Você pode resolver os serviços de uma das duas maneiras, dependendo se você se sentir à vontade para que seus serviços dependam deste pacote de não:

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
}

Eu projetei especificamente esta biblioteca para funcionar bem com Microsoft.Extensions.DependencyInjection - por exemplo:

  1. Quando você registra serviços nomeados, qualquer tipo que você registra pode ter construtores com parâmetros - eles serão satisfeitos via DI, da mesma maneira que AddTransient<>, AddScoped<>e AddSingleton<>métodos funcionam normalmente.

  2. Para serviços nomeados transitórios e com escopo definido, o registro cria um ObjectFactorypara que ele possa ativar novas instâncias do tipo muito rapidamente quando necessário. Isso é muito mais rápido que outras abordagens e está alinhado com o modo como Microsoft.Extensions.DependencyInjection faz as coisas.

Darrell
fonte
2

Minha solução para o que vale a pena ... considerou mudar para o Castelo Windsor, pois não posso dizer que gostei de nenhuma das soluções acima. Desculpe!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

Crie suas várias implementações

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

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

Cadastro

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

Uso do construtor e da instância ...

public class Whatever
{
   private IStage ClassA { get; }

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

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

Estendendo a solução de @rnrneverdies. Em vez de ToString (), as seguintes opções também podem ser usadas: 1) Com a implementação de propriedades comuns, 2) Um serviço de serviços sugerido por @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
fonte
1

Depois de ler as respostas aqui e os artigos em outros lugares, consegui fazê-lo funcionar sem restrições. Quando você tem várias implementações da mesma interface, o DI as adiciona a uma coleção, portanto, é possível recuperar a versão que você deseja da coleção usando 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
fonte
Derrota o objetivo da IoC. Você também pode escrever:var serviceA = new ServiceA();
James Curran
2
@JamesCurran não se o ServiceA tiver dependências ou se você quiser testar a classe da unidade.
Jorn.Beyers
0

Embora a implementação pronta para uso não a ofereça, aqui está um projeto de amostra que permite registrar instâncias nomeadas e injetar INamedServiceFactory em seu código e extrair instâncias por nome. Ao contrário de outras soluções faciais aqui, ele permitirá que você registre várias instâncias da mesma implementação, mas configuradas de maneira diferente

https://github.com/macsux/DotNetDINamedInstances

Andrew Stakhov
fonte
0

Que tal um serviço para serviços?

Se tivéssemos uma interface INamedService (com propriedade .Name), poderíamos escrever uma extensão IServiceCollection para .GetService (nome da string), onde a extensão usaria esse parâmetro e faria um .GetServices () sozinho e em cada retorno instância, localize a instância cujo INamedService.Name corresponde ao nome fornecido.

Como isso:

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

Portanto, seu IMyService deve implementar INamedService, mas você obterá a resolução baseada em chave desejada, certo?

Para ser justo, ter que ter essa interface INamedService parece feio, mas se você quiser ir além e tornar as coisas mais elegantes, um [NamedServiceAttribute ("A")] na implementação / classe pode ser encontrado pelo código neste extensão, e iria funcionar tão bem. Para ser ainda mais justo, o Reflection é lento, portanto uma otimização pode estar em ordem, mas honestamente isso é algo que o mecanismo de DI deveria estar ajudando. Velocidade e simplicidade são os principais contribuintes do TCO.

Em suma, não há necessidade de uma fábrica explícita, porque "encontrar um serviço nomeado" é um conceito tão reutilizável e as classes de fábrica não são dimensionadas como uma solução. E um Func <> parece bom, mas um bloco de comutação é tão ruim e, novamente, você estará escrevendo Funcs com a mesma frequência que estaria escrevendo Fábricas. Comece simples, reutilizável, com menos código e, se isso não resultar em você, vá para o complexo.

Craig Brunetti
fonte
2
Isso é chamado de padrão de localizador de serviço e normalmente não é o melhor caminho a ser percorrido, a menos que seja absolutamente necessário
Joe Phillips
@JoePhillips Você tem alguma opinião sobre por que não é uma boa solução? eu amo a elegância disso. A única desvantagem em que consigo pensar é que eu cria uma instância de todas elas toda vez que você obtém uma.
Peter
2
@ Peter A principal razão é porque é muito, muito difícil trabalhar. Se você está passando um objeto serviceLocator para uma classe, não é óbvio quais dependências a classe usa, pois está obtendo todas elas de um objeto "deus" mágico. Imagine ter que encontrar referências do tipo que você deseja alterar. Essa capacidade basicamente desaparece quando você obtém tudo por meio de um objeto localizador de serviço. Injeção de construtor é muito mais clara e confiável
Joe Phillips
Não sei. A obviedade não é um sinal negativo para mim ... porque se eu me preocupasse em acompanhar como meus componentes aproveitam suas dependências, eu teria testes de unidade para isso ... testes que não apenas se referem a cada dependência, mas nos ajudam a entender COMO cada dependência é necessária. De que outra forma você estará ciente disso, lendo construtores?!?
Craig Brunetti
0

Encontrei o mesmo problema e trabalhei com uma extensão simples para permitir serviços nomeados. Você pode encontrá-lo aqui:

Ele permite que você adicione quantos serviços (nomeados) desejar:

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

A biblioteca também permite implementar facilmente um "padrão de fábrica" ​​como este:

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

Espero que ajude

Subgurim
fonte
0

Eu criei minha própria extensão sobre a extensão IServiceCollectionusada 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é uma instância singleton que os mapas IService> NameOfService> Implementationonde uma interface poderia ter muitas implementações com nomes diferentes, o que permite registar os tipos do que podemos resolver quando wee necessidade e é uma abordagem diferente do que os serviços resolve múltiplas para selecionar o que queremos.

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

Para registrar um novo serviço:

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

Para resolver o serviço, precisamos de uma extensão IServiceProvidercomo esta.

/// <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 resolver:

serviceProvider.GetService<IWithdrawalHandler>(serviceName);

Lembre-se de que o serviceProvider pode ser injetado dentro de um construtor em nosso aplicativo como IServiceProvider .

Eu espero que isso ajude.

svladimirrc
fonte