A injeção de dependência (DI) é um dos pilares do desenvolvimento moderno com .NET, promovendo desacoplamento e facilitando testes e manutenção. Em aplicações ASP.NET Core, é comum registrarmos nossos serviços manualmente utilizando instruções como “builder.Services.AddScoped<IInterface, Implementacao>();” Mas… e se pudéssemos automatizar esse processo?

Neste artigo, você aprenderá como usar Reflection para registrar automaticamente serviços, com base em convenções de nomes e interfaces, reduzindo repetição de código e aumentando a escalabilidade do projeto.

O que é Reflection?

O Reflection é uma funcionalidade da plataforma .NET que permite inspecionar, acessar e manipular metadados de tipos em tempo de execução. Em termos simples, com o Reflection, podemos descobrir informações sobre classes, interfaces, métodos, propriedades, atributos e até criar instâncias de objetos dinamicamente — sem saber seus tipos em tempo de compilação.

Por exemplo, imagine que sua aplicação possui dezenas de serviços que seguem o padrão “INomeServico” e “NomeServico”. Com Reflection, podemos encontrar todos os tipos no projeto que seguem essa convenção, identificar suas interfaces e registrar automaticamente no container de injeção de dependência do ASP.NET Core.

Essa análise é possível graças a classes presentes no namespace “System.Reflection”, como:

Type: representa informações sobre um tipo (classe, interface, enum etc.);

Assembly: representa um assembly carregado na aplicação;

MethodInfo, PropertyInfo, ConstructorInfo: fornecem detalhes sobre os membros do tipo;

Attribute: permite acessar atributos personalizados aplicados a tipos e membros.

Vamos ver, na próxima seção, como aplicar esses conceitos na prática para automatizar o registro de serviços no ASP.NET Core usando Reflection com convenções.

Criando as interfaces base para os serviços

Para que o Reflection funcione de forma segura e organizada, vamos adotar uma convenção simples: todos os serviços que devem ser registrados automaticamente implementarão uma interface de marcação chamada “IService”. Essa interface não precisa declarar membros, ela serve apenas para identificar tipos que fazem parte do nosso sistema de injeção automática.

				
					public interface IService
{
}

				
			

Além disso, vamos definir duas interfaces reais de serviço que herdam de “IService”, que serão utilizadas mais adiante.

				
					public interface IPessoaService : IService
{
    Task<IEnumerable<string>> BuscarPessoas();
}

public interface IProdutoService : IService
{
    Task<IEnumerable<string>> BuscarProdutos();
}

				
			

Criando as implementações concretas dos serviços

Agora que definimos nossas interfaces “IPessoaService” e “IProdutoService”, que herdam da interface de marcação “IService”, vamos criar as implementações concretas: “PessoaService” e “ProdutoService”.

Essas classes seguem uma convenção de nomenclatura fundamental para o registro automático via Reflection: a classe concreta deve ter o mesmo nome da interface, mas sem o prefixo I.

Essa convenção será utilizada mais adiante para identificar qual classe deve ser registrada para qual interface, sem que precisemos fazer isso manualmente.

				
					public class PessoaService : IPessoaService
{
    public async Task<IEnumerable<string>> BuscarPessoas()
    {
        return await Task.FromResult(new List<string>
        {
            "João da Silva",
            "Maria Oliveira",
            "Carlos Souza"
        });
    }
}

public class ProdutoService : IProdutoService
{
    public async Task<IEnumerable<string>> BuscarProdutos()
    {
        return await Task.FromResult(new List<string>
        {
            "Notebook Dell",
            "Monitor LG",
            "Teclado Mecânico"
        });
    }
}

				
			

Agora vamos criar uma nova classe chamada “ServiceCollectionExtensions” onde vamos implementar o Reflection:

 

 

Registrando serviços automaticamente com Reflection

Crie um método de extensão que adiciona os serviços dinamicamente. Vamos chamá-la de “RegistrarServicosComReflection”:

				
					public static class ServiceCollectionExtensions
{
    public static IServiceCollection RegistrarServicosComReflection(this IServiceCollection services)
    {
	    //Nosso código
    }
}

				
			

Agora vamos adicionar o nosso código:

				
					var tiposEncontrados = typeof(IService).Assembly
    .GetTypes()
    .Where(t => typeof(IService).IsAssignableFrom(t));

				
			

Neste trecho, usamos “typeof(IService).Assembly” para obter o assembly onde a interface “IService” está definida. Em seguida, usamos o método ”GetTypes()” para recuperar todos os tipos definidos neste assembly (classes, interfaces, enums, etc).

Depois, com “Where(t => typeof(IService).IsAssignableFrom(t))”, filtramos apenas os tipos que implementam ou herdam de “IService”. Isso inclui tanto as interfaces concretas (IPessoaService, IProdutoService) quanto suas implementações (PessoaService, ProdutoService).

				
					var interfaces = tiposEncontrados.Where(t => t.IsInterface).ToList();
var classesConcretas = tiposEncontrados
    .Where(t => t.IsClass && !t.IsAbstract)
    .ToList();

				
			

Aqui fazemos uma separação entre os tipos encontrados no passo anterior:

interfaces: seleciona apenas os tipos que são interfaces (t.IsInterface == true), como “IPessoaService” ou “IProdutoService”.

classesConcretas: seleciona apenas as classes que são concretas, ou seja, não são abstratas e são classes reais que podem ser instanciadas (IsClass == true && IsAbstract == false). Exemplo: “PessoaService”, “ProdutoService”.

Essa separação é essencial para sabermos qual implementação deve ser registrada para qual interface.

				
					foreach (var implementacao in classesConcretas)
{
    var nomeEsperado = $"I{implementacao.Name}";
    var interfaceCorrespondente = interfaces.FirstOrDefault(i => i.Name == nomeEsperado);

				
			

Neste laço “foreach”, percorremos cada classe concreta que foi encontrada.

Para localizar a interface correspondente, usamos a convenção de nome que assumimos anteriormente onde a interface tem o mesmo nome da classe, com um “I” no início, ou seja, a interface da classe “PessoaService” será “IPessoaService”. Montamos esse nome com “var nomeEsperado = $”I{implementacao.Name}””.

Em seguida, usamos “FirstOrDefault” para buscar entre as interfaces encontradas aquela cujo nome bate com o nome esperado.

				
					if (interfaceCorrespondente != null)
{
     services.AddScoped(interfaceCorrespondente, implementacao);
}
}

				
			

Se a interface correspondente foi encontrada, registramos a combinação de interface e implementação no container de injeção de dependência do ASP.NET Core usando “services.AddScoped(…)”.

Aqui usamos o tempo de vida Scoped, o que significa que será criada uma nova instância do serviço para cada requisição HTTP.

Por fim, é só chamar o método de registro automático no “Program.cs”:

				
					builder.Services.RegistrarServicosComReflection();
				
			

Considerações e boas práticas

Impacto de performance: o Reflection é mais lento do que chamadas diretas. Mas como esse processo ocorre apenas na inicialização  da aplicação.

Convenções obrigatórias: o nome da interface deve seguir a convenção I{NomeDaClasse}. Se você tiver uma classe chamada “UsuarioService”, a interface deve se chamar “IUsuarioService”.

Interface de marcação obrigatória: para que um serviço seja incluído automaticamente na injeção de dependência, ele deve implementar a interface de marcação IService.

Outros tempos de vida: no exemplo implementado, os serviços são registrados apenas com o tempo de vida “Scoped”. Para suportar outros tipos, como “Singleton” ou “Transient”, é necessário adaptar o código para identificar e aplicar o tempo de vida adequado durante o registro.

Acelere a sua carreira conosco!

Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET StartClique aqui

Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET ExpertClique aqui

Conclusão

Usar Reflection para automatizar a injeção de dependências é uma técnica elegante e poderosa, especialmente em projetos com muitos serviços. Apesar de exigir atenção às convenções e ter um custo inicial de performance, ela torna o código mais limpo, enxuto e fácil de manter.