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 Unity
permitem registrar implementações concretas por algumas Key
que 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 Add
método de serviço que aceite um parâmetro key
ou 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:
- A capacidade de registrar instâncias usando uma chave
- A capacidade de injetar dados estáticos nos construtores durante o registro
Update1
ser movido para uma pergunta diferente, pois injetar coisas nos construtores é muito diferente de descobrir qual objeto construirRespostas:
Eu fiz uma solução simples usando
Func
quando me encontrei nessa situação.Em primeiro lugar, declare um delegado compartilhado:
Em seguida, no seu
Startup.cs
, configure os vários registros concretos e um mapeamento manual desses tipos:E use-o de qualquer classe registrada no DI:
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.
fonte
Outra opção é usar o método
GetServices
de extensão deMicrosoft.Extensions.DependencyInjection
.Registre seus serviços como:
Então resolva com um pouco de Linq:
ou
(supondo que
IService
tenha uma propriedade de sequência chamada "Nome")Certifique-se de ter
using Microsoft.Extensions.DependencyInjection;
Atualizar
Fonte AspNet 2.1:
GetServices
fonte
IEnumerable<IService>
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:
Adicione uma dependência ao StructureMap no seu
project.json
:Injete-o no pipeline do ASP.NET dentro
ConfigureServices
e registre suas classes (consulte a documentação)Em seguida, para obter uma instância nomeada, você precisará solicitar o
IContainer
É isso aí.
Para o exemplo construir, você precisa
fonte
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 ).
fonte
Eu enfrentei o mesmo problema e quero compartilhar como o resolvi e por quê.
Como você mencionou, existem dois problemas. O primeiro:
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)
)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:
Primeiro, concordo com você que, dependendo de coisas novas como
IOptions
(e, portanto, do pacoteMicrosoft.Extensions.Options.ConfigurationExtensions
), não é uma boa ideia. Eu já vi algumas discussões sobreIOptions
onde 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:
fonte
IServiceA
no seu caso. Como você está usando métodosIService
apenas, você deve terIService
apenas dependência .services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));
para a primeira classe eservices.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));
para a segunda.Eu simplesmente injeto um IEnumerable
ConfigureServices em Startup.cs
Pasta Serviços
MyController.cs
Extensions.cs
fonte
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 é:
Escrevi um artigo com mais detalhes: Injeção de Dependências no .NET: Uma maneira de solucionar os registros nomeados ausentes
fonte
Um pouco atrasado para esta festa, mas aqui está a minha solução: ...
Startup.cs ou Program.cs se Generic Handler ...
IMyInterface da configuração da interface T
Implementações concretas do IMyInterface of T
Felizmente, se houver algum problema em fazê-lo dessa maneira, alguém indicará por que essa é a maneira errada de fazer isso.
fonte
IMyInterface<CustomerSavedConsumer>
eIMyInterface<ManagerSavedConsumer>
são tipos de serviço diferentes - isso não responde à pergunta dos OPs.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
SNSConfig
appsettings.json
Fábrica SNS
Agora você pode obter o serviço SNS da região que deseja em seu serviço ou controlador personalizado
fonte
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, como
IRepository<T>
, a base para o acesso a dados.Exemplos de interfaces e implementações:
Recipiente:
fonte
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:
E então registre o dicionário com a coleção de serviços:
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)
Agora você pode acessar seus tipos com
ou
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):
(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: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
fonte
IDispose
implementado, quem é responsável pela disposição do serviço? Você registrou o dicionário comoSingleton
desde o meu post acima, mudei para uma classe de fábrica genérica
Uso
Implementação
Extensão
fonte
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 deIServiceAccessor
interface, e tive que adicionar mais algumas extensões ao seguinteIServiceCollection
:O serviço Accessor é semelhante a:
O resultado final, você poderá registrar serviços com nomes ou instâncias nomeadas, como costumávamos fazer com outros contêineres ... por exemplo:
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.
fonte
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:
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:
ou
Eu projetei especificamente esta biblioteca para funcionar bem com Microsoft.Extensions.DependencyInjection - por exemplo:
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<>
eAddSingleton<>
métodos funcionam normalmente.Para serviços nomeados transitórios e com escopo definido, o registro cria um
ObjectFactory
para 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.fonte
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!!
Crie suas várias implementações
Cadastro
Uso do construtor e da instância ...
fonte
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.
fonte
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
.fonte
var serviceA = new ServiceA();
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
fonte
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:
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.
fonte
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:
A biblioteca também permite implementar facilmente um "padrão de fábrica" como este:
Espero que ajude
fonte
Eu criei minha própria extensão sobre a extensão
IServiceCollection
usadaWithName
:ServiceCollectionTypeMapper
é uma instância singleton que os mapasIService
>NameOfService
>Implementation
onde 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.Para registrar um novo serviço:
Para resolver o serviço, precisamos de uma extensão
IServiceProvider
como esta.Quando resolver:
Lembre-se de que o serviceProvider pode ser injetado dentro de um construtor em nosso aplicativo como
IServiceProvider
.Eu espero que isso ajude.
fonte