Compreendendo a injeção de dependência no ASP.NET Core
Um guia detalhado sobre como aproveitar a injeção de dependência para criar aplicativos ASP.NET Core robustos e fáceis de manter.
Introdução
No âmbito do desenvolvimento de software moderno, a Injeção de Dependência (DI) se destaca como um padrão de design fundamental que promove o acoplamento flexível e aprimora a testabilidade e a manutenibilidade de aplicações. O ASP.NET Core, o framework de código aberto e multiplataforma da Microsoft para a construção de aplicações web, adota a DI como um cidadão de primeira classe, fornecendo um contêiner IoC (Inversão de Controle) integrado para gerenciar dependências de forma eficiente.
Este artigo se aprofunda nos princípios da injeção de dependência, como ela é implementada no ASP.NET Core e nas práticas recomendadas para maximizar seus benefícios em seus aplicativos.
O que é injeção de dependência?
Injeção de Dependência é um padrão de design no qual um objeto recebe suas dependências de fontes externas, em vez de criá-las ele mesmo. Essa abordagem desvincula a construção de uma classe de seu comportamento, permitindo:
- Promova acoplamento flexível: as classes dependem de abstrações (interfaces) em vez de implementações concretas.
- Melhore a capacidade de teste: as dependências podem ser simuladas ou stub, simplificando os testes unitários.
- Melhore a manutenibilidade: alterações nas dependências têm impacto mínimo nas classes dependentes.
- Aumente a flexibilidade: troque implementações sem modificar a classe dependente.
Injeção de dependência no ASP.NET Core
O ASP.NET Core fornece um contêiner DI integrado, facilitando o gerenciamento de dependências em toda a sua aplicação. O contêiner é responsável por:
- Registro de serviço: Mapeamento de interfaces para implementações concretas.
- Resolução de serviço: injetando dependências onde necessário.
- Gerenciamento de tempo de vida: controlar o escopo e a vida útil dos serviços.
Registro de serviço
Os serviços são registrados no arquivo Program.cs
usando a interface IServiceCollection
. O registro envolve a especificação do tipo de serviço, do tipo de implementação e do tempo de vida do serviço.
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Register application services.
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddSingleton<ILoggingService, LoggingService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapControllers();
app.Run();
Vida útil do serviço
Entender a vida útil dos serviços é crucial para gerenciar recursos e garantir o desempenho dos aplicativos.
-
Singleton: uma única instância é criada e compartilhada durante todo o tempo de vida do aplicativo.
c sustenido
builder.Services.AddSingleton<IService, ServiceImplementation>();
-
Com escopo: uma nova instância é criada por solicitação do cliente (solicitação HTTP).
c sustenido
builder.Services.AddScoped<IService, ServiceImplementation>();
-
Transitório: Uma nova instância é criada toda vez que o serviço é solicitado.
c sustenido
builder.Services.AddTransient<IService, ServiceImplementation>();
Escolhendo a Vida Certa
- Singleton: use para serviços sem estado ou serviços que mantêm estado compartilhado entre solicitações.
- Com escopo: ideal para serviços que devem ser exclusivos por solicitação, mas reutilizados dentro dessa solicitação.
- Transitório: melhor para serviços leves, sem estado e sem estado compartilhado.
Serviços de consumo
Uma vez registrados, os serviços podem ser consumidos via injeção de construtor , que é o método mais comum no ASP.NET Core.
Injetando em Controladores
// Controllers/HomeController.cs
using Microsoft.AspNetCore.Mvc;
public class HomeController : Controller
{
private readonly IProductService _productService;
ILoggingService privado somente leitura _loggingService;
HomeController público ( IProductService productService, ILoggingService loggingService )
{
_productService = serviçoDeProduto;
_loggingService = Serviço de registro;
}
Índice IActionResult público ()
{
var produtos = _productService.GetAllProducts();
_loggingService.Log( "Todos os produtos foram obtidos." );
retornar View(produtos);
}
}
Injetando em outros serviços
Os serviços também podem depender de outros serviços.
// Services/ProductService.cs
public class ProductService : IProductService
{
private readonly IDataRepository _dataRepository;
public ProductService ( IDataRepository dataRepository )
{
_dataRepository = dataRepository;
}
public IEnumerable<Product> GetAllProducts ()
{
return _dataRepository.GetProducts();
}
}
Benefícios da injeção de construtor
- Dependências explícitas: todos os serviços necessários são claramente definidos.
- Imutabilidade: as dependências são somente leitura, evitando modificações não intencionais.
- Testabilidade: mais fácil simular dependências durante testes unitários.
Melhores Práticas
-
Use interfaces para abstrações
Dependa de interfaces em vez de classes concretas para promover acoplamento flexível.
c sustenidopublic interface IEmailService { void SendEmail ( string to, string subject, string body ) ; }
classe pública EmailService : IEmailService { // Implementação } -
Evite dependências cativas
Certifique-se de que o tempo de vida de um serviço não capture dependências com tempos de vida mais curtos, o que pode levar a comportamentos inesperados.
-
Parâmetros do Construtor de Limite
Se uma classe exigir muitas dependências, considere refatorá-la para reduzir a complexidade.
-
Evite o padrão do localizador de serviço
Não injete
IServiceProvider
para resolver serviços manualmente; isso oculta dependências e dificulta os testes. -
Descarte os serviços corretamente
Deixe que o contêiner DI gerencie o descarte de serviços, especialmente para serviços com escopo e transitórios.
Teste Unitário com Injeção de Dependência
A injeção de dependência simplifica os testes unitários permitindo que você injete implementações simuladas de dependências.
Exemplo: Testando um Controlador
using Xunit;
using Moq;
public class HomeControllerTests
{
[ ]
public void Index_ReturnsViewWithProducts ()
{
// Arrange
var mockProductService = new Mock<IProductService>();
mockProductService.Setup(service => service.GetAllProducts())
.Returns(GetTestProducts());
var mockLoggingService = new Mock<ILoggingService>();
var controlador = novo HomeController(mockProductService.Object, mockLoggingService.Object);
// Agir
var resultado = controller.Index();
// Afirmar
var viewResult = Assert.IsType<ViewResult>(resultado);
var modelo = Assert.IsAssignableFrom<IEnumerable<Produto>>(viewResult.ViewData.Model);
Assert.Equal( 2 , modelo.Count());
}
privado IEnumerable<Produto> GetTestProducts ()
{
retornar nova Lista<Produto>
{
novo Produto { Id = 1 , Nome = "Produto de Teste 1" },
novo produto { Id = 2 , Nome = "Produto de teste 2" }
};
}
}
Cenários Avançados
Registro de Serviço Condicional
Registre serviços com base no ambiente ou configuração.
if (builder.Environment.IsDevelopment())
{
builder.Services.AddSingleton<IEmailService, MockEmailService>();
}
else
{
builder.Services.AddSingleton<IEmailService, EmailService>();
}
Implementações múltiplas
Quando você tem várias implementações de uma interface, você pode usar serviços nomeados ou padrões de resolução baseados em chaves.
Usando contêineres de terceiros
Se o contêiner integrado não tiver determinados recursos, você poderá substituí-lo por um contêiner de terceiros, como Autofac ou Unity.
Armadilhas comuns
- Tempos de vida mal configurados: tenha cuidado ao injetar serviços com tempos de vida mais longos em serviços de duração mais curta.
- Dependências circulares: evite situações em que dois ou mais serviços dependem um do outro direta ou indiretamente.
- Uso excessivo de serviços Singleton: os serviços Singleton devem ser sem estado ou lidar com sua própria segurança de thread.
Conclusão
Injeção de Dependência é um padrão poderoso que aprimora a modularidade e a testabilidade de seus aplicativos ASP.NET Core. Ao entender como registrar e injetar serviços corretamente, você pode criar aplicativos mais fáceis de manter e estender.
Adotar DI em seus projetos resulta em código mais limpo, menos bugs e uma arquitetura mais flexível que pode se adaptar a requisitos em constante mudança.
Recursos adicionais
- Documentação da Microsoft: Injeção de dependência no ASP .NET Core
- Fundamentos do ASP.NET Core: Explore a documentação oficial para saber mais sobre os recursos do ASP.NET Core.
- Tutoriais da comunidade: procure blogs e tutoriais que forneçam exemplos práticos e cenários avançados de DI.
Sobre o autor
Pedro Martins é um desenvolvedor de software especializado em tecnologias .NET. Apaixonado por código limpo e melhores práticas, Pedro Martins gosta de compartilhar conhecimento por meio de textos e palestras.
Quer otimizar suas habilidades em software? Visite askpedromartins.com para obter conselhos de especialistas e soluções personalizadas para suas necessidades de desenvolvimento.