Entender a injeção de dependência
Os aplicativos ASP.NET Core geralmente precisam acessar os mesmos serviços em vários componentes. Por exemplo, vários componentes podem precisar acessar um serviço que busca dados de um banco de dados. O ASP.NET Core usa um contêiner de injeção de dependência (DI) interno para gerenciar os serviços que o aplicativo usa.
Injeção de dependência e inversão de controle (IoC)
O padrão de injeção de dependência é uma forma de Inversão de Controle (IoC). No padrão de injeção de dependência, um componente recebe suas dependências de fontes externas em vez de criá-las por conta própria. Esse padrão desacopla o código da dependência, o deixando o código mais fácil de testar e manter.
Considere o seguinte arquivo Program.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using MyApp.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<PersonService>();
var app = builder.Build();
app.MapGet("/",
(PersonService personService) =>
{
return $"Hello, {personService.GetPersonName()}!";
}
);
app.Run();
E o arquivo PersonService.cs:
namespace MyApp.Services;
public class PersonService
{
public string GetPersonName()
{
return "John Doe";
}
}
Para entender o código, comece com o código app.MapGet destacado. Este código mapeia solicitações HTTP GET para a URL raiz (/) para um delegado que retorna uma mensagem de saudação. A assinatura do delegado define um parâmetro PersonService chamado personService. Quando o aplicativo é executado e um cliente solicita a URL raiz, o código dentro do delegado depende de um serviço PersonService para obter texto a ser incluído na mensagem de saudação.
De onde o delegado obtém o serviço PersonService? Ele é fornecido implicitamente pelo contêiner de serviço. A linha builder.Services.AddSingleton<PersonService>() destacada informa ao contêiner de serviço que deve criar uma nova instância da classe PersonService ao iniciar o aplicativo e fornecê-la a qualquer componente que precise dela.
Qualquer componente que precise do serviço PersonService pode declarar um parâmetro do tipo PersonService em sua assinatura de delegado. O contêiner de serviço fornecerá automaticamente uma instância da classe PersonService quando o componente for criado. O delegado não cria a instância PersonService por conta própria; ele apenas utiliza a instância fornecida pelo contêiner de serviço.
Interfaces e injeção de dependência
Para evitar dependências em uma implementação de serviço específica, você pode configurar um serviço para uma interface específica e, em seguida, depender apenas da interface. Essa abordagem oferece flexibilidade para trocar a implementação do serviço, facilitando os testes e a manutenção do código.
Considere uma interface para a classe PersonService:
public interface IPersonService
{
string GetPersonName();
}
Essa interface define o método único GetPersonName, que retorna um string. Esta classe PersonService implementa a interface IPersonService:
internal sealed class PersonService : IPersonService
{
public string GetPersonName()
{
return "John Doe";
}
}
Em vez de registrar a classe PersonService diretamente, você pode registrá-la como uma implementação da interface IPersonService:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IPersonService, PersonService>();
var app = builder.Build();
app.MapGet("/",
(IPersonService personService) =>
{
return $"Hello, {personService.GetPersonName()}!";
}
);
app.Run();
Este exemplo de Program.cs difere do exemplo anterior em dois pontos:
- A instância
PersonServiceé registrada como uma implementação da interfaceIPersonService, em vez de registrar diretamente a classePersonService. - A assinatura do delegado agora espera um parâmetro
IPersonServiceem vez de um parâmetroPersonService.
Quando o aplicativo é executado e um cliente solicita a URL raiz, o contêiner de serviço fornece uma instância da classe PersonService porque ela está registrada como a implementação da interface IPersonService.
Dica
Pense no IPersonService como um contrato. É responsável por definir os métodos e as propriedades que uma implementação deve ter. O delegado deseja uma instância de IPersonService. No entanto, não se importa com a implementação subjacente, apenas que a instância tenha os métodos e as propriedades definidos no contrato.
Teste com injeção de dependência
O uso de interfaces facilita o teste de componentes isoladamente. Você pode criar uma implementação fictícia da interface IPersonService para fazer os testes. Quando você registra a implementação fictícia no teste, o contêiner de serviço fornece a implementação fictícia para o componente que está sendo testado.
Por exemplo, digamos que, em vez de retornar uma cadeia de caracteres embutida em código, o método GetPersonName na classe PersonService busque o nome de um banco de dados. Para testar o componente que depende da interface IPersonService, você pode criar uma implementação fictícia da interface IPersonService que retorna uma cadeia de caracteres embutida em código. O componente em teste não percebe a diferença entre a implementação real e a fictícia.
Suponha também que seu aplicativo mapeia um endpoint da API que retorna uma mensagem de saudação. O ponto de extremidade depende da interface IPersonService para obter o nome da pessoa a ser saudada. O código que registra o serviço IPersonService e mapeia o ponto de extremidade de API pode ser assim:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IPersonService, PersonService>();
var app = builder.Build();
app.MapGet("/", (IPersonService personService) =>
{
return $"Hello, {personService.GetPersonName()}!";
});
app.Run();
Isso é semelhante ao exemplo anterior com IPersonService. O delegado espera um parâmetro IPersonService, que o contêiner de serviço fornece. Conforme mencionado anteriormente, suponha que o PersonService que implementa a interface busque o nome da pessoa a ser saudada em um banco de dados.
Agora, considere o seguinte teste XUnit que testa o mesmo endpoint de API:
Dica
Não se preocupe se você não tiver familiaridade com XUnit ou Moq. Escrever testes de unidade está fora do escopo deste módulo. Este exemplo serve apenas para ilustrar como a injeção de dependência pode ser usada em testes.
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using MyWebApp;
using System.Net;
public class GreetingApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public GreetingApiTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task GetGreeting_ReturnsExpectedGreeting()
{
//Arrange
var mockPersonService = new Mock<IPersonService>();
mockPersonService.Setup(service => service.GetPersonName()).Returns("Jane Doe");
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
services.AddSingleton(mockPersonService.Object);
});
}).CreateClient();
// Act
var response = await client.GetAsync("/");
var responseString = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Hello, Jane Doe!", responseString);
}
}
O teste anterior:
- Cria uma implementação fictícia da interface
IPersonServiceque retorna uma cadeia de caracteres embutida em código. - Registra a implementação fictícia no contêiner de serviço.
- Cria um cliente HTTP para fazer uma solicitação ao endpoint da API.
- Afirma que a resposta do endpoint da API é conforme o esperado.
O teste não se importa com como a classe PersonService obtém o nome da pessoa a ser saudada. Apenas se preocupa que o nome esteja incluído na mensagem de saudação. O teste usa uma implementação fictícia da interface IPersonService para isolar o componente sendo testado da implementação real do serviço.