C#
- Múltiplas implementações de uma interface
![]() |
Neste artigo vamos discutir como fazer múltiplas implementações de uma interface usando o container de injeção de dependência nativo da plataforma .NET. |
O contêiner IoC (inversão de controle) embutido na plataforma .NET e muito usado em aplicações ASP.NET Core pode não ter tantos recursos como outros contêineres IoC como o AutoFac ou o Unity, mas ele pode ser usado para atender aos seus requisitos na maioria dos casos.
No entanto, o
contêiner IoC integrado, apresenta o seguinte problema: Ele não nos permite
registrar vários serviços e, em seguida, recuperar uma instância de um serviço
específica em tempo de execução.
Existem alguns contêineres IoC que permitem registrar tipos concretos usando uma
chave exclusiva que distingue as instâncias desses tipos. No entanto, o
contêiner IoC integrado da ASP.NET Core não tem suporte para isso.
Portanto,
registrar serviços que possuem uma interface comum e resolvê-los em tempo de
execução não é simples.
Vamos entender isso com um exemplo.
Vamos supor que temos em um projeto ASP .NET Core MVC seguinte interface definida:
public interface IRegistraLog {
public bool Registrar(string texto); |
Esta interface é implementada por 3 classes :
1- ArquivoRegistraLog
using System;
namespace AspnMultiplasInterfaces.Logs |
2- DatabaseRegistraLog
using System;
namespace AspnMultiplasInterfaces.Logs |
3- EventoRegistraLog
using System;
namespace AspnMultiplasInterfaces.Logs |
A classe
ArquivoRegistraLog é usada para registrar dados em
um arquivo, a classe DatabaseRegistraLog é usada
para registrar dados em um banco de dados e a classe
EventoRegistraLog é usada para registrar dados no log de eventos para que
os logs possam ser visualizados usando a ferramenta Event Viewer.
Agora, suponha que você tenha adicionado instâncias dessas classes como serviços
com escopo no método ConfigureServices da classe
Startup, conforme mostrado abaixo :
public void ConfigureServices(IServiceCollection services) { services.AddScoped<IRegistraLog, ArquivoRegistraLog>(); services.AddScoped<IRegistraLog, DatabaseRegistraLog>(); services.AddScoped<IRegistraLog, EventoRegistraLog>();
services.AddControllersWithViews(); |
Vamos agora fazer a injeção de dependência desses serviços no controlador HomeController e verificar qual o comportamento:
public class HomeController : Controller { public HomeController(IRegistraLog arquivoLog, IRegistraLog dbLog, IRegistraLog eventoLog) { var arq = arquivoLog; var db = dbLog; var evt = eventoLog; } ... } |
Estamos injetando os 3 serviços no construtor e a seguir obtendo a instância de cada serviço.
Colocando um breakpoint e executando o projeto, ao inspecionar o valor das variáveis arq, db e evt iremos notar que em todas o valor atribuído será a instância de EventoRegistraLog :
Isso esta acontecendo porque o Contâiner DI esta injetando apenas objetos EventoRegistraLog pois esta foi a última implementação registrada no método ConfigureServices.
Apresentando o solução
Vemos então esta limitação do Contêiner DI nativo da plataforma .NET.
Como superamos essa limitação do contêiner IoC integrado no ASP.NET Core ?
Uma forma de contornar essa limitação é usar uma coleção de serviços IEnumerable para registrar os serviços e um delegate para recuperar uma instância de serviço específica.
public class HomeController : Controller
{
public HomeController(IEnumerable<IRegistraLog> registraLogs)
{
foreach (var registraLog in registraLogs)
{
var tipoLog = registraLog.GetType();
}
}
...
}
|
No código acima temos que as 3 instâncias de cada serviço será injetada no construtor e podemos verificar o tipo do objeto para decidir qual implementação do serviço usar.
Usando um delegate para retornar uma instância específica
Podemos retornar uma instância específica definindo uma enumeração com os tipos de serviços:
public enum TipoServico { ArquivoLog, DatabaseLog, EventoLog } |
A seguir definimos um delegate ServiceResolver e no método ConfigureServices() definimos o registro de cada serviço usando o tempo de serviço AddScoped, e , a seguir podemos retornar a instância de cada serviço dependendo do tipo de serviço selecionando em tempo de execução:
public class Startup { private delegate IRegistraLog ServiceResolver(TipoServico tipoServico); public void ConfigureServices(IServiceCollection services) { services.AddScoped<ArquivoRegistraLog>(); services.AddScoped<DatabaseRegistraLog>(); services.AddScoped<EventoRegistraLog>();
services.AddTransient<ServiceResolver>(serviceProvider => nomeServico => services.AddControllersWithViews(); |
Ao final usamos a injeção de dependência para resolver o tipo de serviço :
public class HomeController : Controller { public HomeController(Func<TipoServico, IRegistraLog> serviceResolver) { var service = serviceResolver(TipoServico.ArquivoLog); } ... } |
Outra solução possível é usar um parâmetro de tipo genérico na interface.
Pegue o código
do projeto aqui :
AspnMultiplasInterfaces.zip
"E eu, quando
o vi, caí a seus pés como morto; e ele pôs sobre mim a sua destra,
dizendo-me: Não temas; Eu sou o primeiro e o último; E o que vivo e fui
morto, mas eis aqui estou vivo para todo o sempre. Amém. E tenho as chaves
da morte e do inferno."
Apocalipse 1:17,18
Referências:
NET - Unit of Work - Padrão Unidade de ...
NET - O padrão de projeto Decorator
NET - Padrão de Projeto Builder
C# - O Padrão Strategy (revisitado)
NET - O padrão de projeto Command
NET - Apresentando o padrão Repository