DI - Múltiplas implementações da mesma interface


   Hoje veremos como trabalhar com múltiplas implementações da mesma interface.

Hoje veremos como registrar várias implementações de uma interface com o contêiner IoC da plataforma .NET em uma aplicação ASP.NET Core e recuperar um serviço específico em tempo de execução.


O contêiner de injeção de dependência nativo da plataforma .NET realiza um bom trabalho e atende a praticamente a maioria das necessidades relacionadas com o serviço de injeção de dependência. No entanto, lidar com várias implementações de uma interface ao trabalhar com injeção de dependência no ASP.NET Core é um pouco complicado.

O contêiner IoC nativo da plataforma .NET não permite realizar o registro de vários serviços e, em seguida, recuperar uma instância de um serviço específico 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 interno da plataforma .NET e usado pela ASP.NET Core não tem suporte a este recurso. Portanto, registrar serviços que possuem uma interface comum e resolvê-los em tempo de execução não é simples.

Para entender o problema vamos criar um projeto ASP .NET Core Web API no .NET 6.

Abra o VS 2022, clique em New Project e selecione o template ASP .NET Core Web API e clique em Next;

Informe o nome Api_DI e clique em Next;

A seguir selecione o Framework, Authentication Type e demais configurações conforme mostra a figura:

Crie uma pasta Services no projeto e a seguir crie a interface ICustomLogger:

namespace Api_DI.Services;

public interface ICustomLogger
{
    public string Write(string data);
}

A seguir vamos criar 3 implementações desta interface criando as classes concretas:

  1. FileLogger
  2. DBLogger
  3. EventLogger

1- FileLogger

namespace Api_DI.Services;

public class IFileLogger : ICustomLogger
{
   public string Write(string data)
   {
            return data;
   }
}

2- DBLogger

namespace Api_DI.Services;

public class DBLogger : ICustomLogger
{
   public string Write(string data)
   {
        return data;
    }
}

3- EventLogger

namespace Api_DI.Services;

public class EventLogger : ICustomLogger
{
   public string Write(string data)
  {
         return data;
   }
}

A classe FileLogger é usada para registrar dados em um arquivo, a classe DbLogger é usada para registrar dados em um banco de dados e a classe EventLogger é usada para registrar dados no log de eventos.

Agora vamos registrar esses serviços no contêiner DI nativo da plataforma .NET. Para isso vamos abrir o arquivo Program e definir o seguinte código:

using Api_DI.Services;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddScoped<ICustomLogger, FileLogger>();
builder.Services.AddScoped<ICustomLogger, DBLogger>();
builder.Services.AddScoped<ICustomLogger, EventLogger>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

 

A seguir vamos criar um controlador HomeController na pasta Controllers e definir o código abaixo para usar as instâncias dos serviços injetados no construtor do controlador:

using Api_DI.Services;
using Microsoft.AspNetCore.Mvc;
namespace Api_DI.Controllers;

[Route("api/[controller]")]
[ApiController]
public class HomeController : ControllerBase
{
    public HomeController(ICustomLogger fileLogger,
                                         ICustomLogger dbLogger,
                                         ICustomLogger eventLogger)
    {
        var obj1 = fileLogger;
        var obj2 = dbLogger;
        var obj3 = eventLogger;
    }

    [HttpGet]
    public ActionResult<string> Teste()
    {
        return "Teste";
    }
}

Se colocarmos um breakpoint no construtor e executarmos o projeto veremos que a instância obtida para obj1 , obj2 e obj3 será a instância de eventLogger :



Isso ocorre porque ele foi injetado por último e assim prevalece sobre os demais. Assim percebemos que usando a abordagem padrão não há como obter uma instância específica.

A seguir veremos uma implementação para contornar esta limitação do contêiner DI nativa da plataforma .NET.

Resolvendo o problema

A abordagem usada para resolver o problema vai usar uma enumeração e um delegate Func<> para registrar os serviços usando uma chave para identificar cada serviço com base na enumeração definida.

Vamos criar na pasta Services uma enumeração chamada ServiceEnum para identificar cada serviço :

namespace Api_DI.Services;

public enum ServiceEnum
{
    File,
    Database,
    Event

A seguir vamos registrar os serviços na classe Program e definir um serviço usando um delegate Func<ServiceEnum,ICustomLogger> para poder retornar instâncias das classes FileLogger, DbLogger e EventLogger dependendo do tipo de serviço selecionado em tempo de execução.

builder.Services.AddScoped<FileLogger>();
builder.Services.AddScoped<DBLogger>();
builder.Services.AddScoped<EventLogger>();

builder.Services.AddTransient<Func<ServiceEnum, ICustomLogger>>(serviceProvider => key =>
{
    switch (key)
    {
        case ServiceEnum.File:
            return serviceProvider.GetService<FileLogger>();
        case ServiceEnum.Database:
            return serviceProvider.GetService<DBLogger>();
        case ServiceEnum.Event:
            return serviceProvider.GetService<EventLogger>();
        default:
            return null;
    }
})

Agora vamos criar um controlador HomeController e usar as instâncias específicas de cada serviço:

using Api_DI.Services;
using Microsoft.AspNetCore.Mvc;
namespace Api_DI.Controllers;

[Route("api/[controller]")]
[ApiController]
public class HomeController : ControllerBase
{
    private readonly ICustomLogger file;
    private readonly ICustomLogger db;
    private readonly ICustomLogger evt;
    public HomeController(Func<ServiceEnum, ICustomLogger> serviceResolver)
    {
        file = serviceResolver(ServiceEnum.File);
        db = serviceResolver(ServiceEnum.Database);
        evt = serviceResolver(ServiceEnum.Event);
    }

    [HttpGet("file")]
    public ActionResult<string> File()
    {
        return file.Write("Serviço FileLogger ");
    }
   

    [HttpGet("db/{id:int}")]
    public ActionResult<string> Database(int id)
    {
        return db.Write("Serviço DBLogger ") + id.ToString();
    }

    [HttpGet("event/{nome}")]
    public ActionResult<string> Event(string nome)
    {
        return evt.Write("Serviço EventLogger ") + nome;
    }
}

Executando o projeto teremos o seguinte resultado:

Executando cada endpoint iremos obter a instância específica do serviço e executar a operação desejada.

Pegue o projeto completo aqui :  Api_DI.zip

E estamos conversados...

"Todo aquele, pois, que escuta estas minhas palavras, e as pratica, assemelhá-lo-ei ao homem prudente, que edificou a sua casa sobre a rocha;"
Mateus 7:24

Referências:


José Carlos Macoratti