Compreender a injeção de dependência
ASP.NET aplicativos principais 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 interno de injeção de dependência (DI) para gerenciar os serviços que um aplicativo usa.
Injeção de dependência e Inversão de Controlo (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 separa o código da dependência, o que torna 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 seguinte 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 realçado app.MapGet . Esse 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 PersonService parâmetro chamado personService. Quando o aplicativo é executado e um cliente solicita a URL raiz, o código dentro do delegado depende do PersonService serviço para obter algum texto para incluir na mensagem de saudação.
Onde o delegado obtém o PersonService serviço? É implicitamente fornecido pelo contêiner de serviço. A linha realçada builder.Services.AddSingleton<PersonService>() informa ao contêiner de serviço para criar uma nova instância da PersonService classe quando o aplicativo for iniciado e para fornecer essa instância a qualquer componente que precise dela.
Qualquer componente que precise do PersonService serviço 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 PersonService classe quando o componente for criado. O delegado não cria a PersonService instância em si, apenas usa a instância que o contêiner de serviço fornece.
Interfaces e injeção de dependência
Para evitar dependências em uma implementação de serviço específica, você pode, em vez disso, configurar um serviço para uma interface específica e, em seguida, depender apenas da interface. Essa abordagem oferece a flexibilidade de trocar a implementação do serviço, o que torna o código mais testável e fácil de manter.
Considere uma interface para a PersonService classe:
public interface IPersonService
{
string GetPersonName();
}
Essa interface define o método único, GetPersonName, que retorna um stringarquivo . Esta PersonService classe implementa a IPersonService interface:
internal sealed class PersonService : IPersonService
{
public string GetPersonName()
{
return "John Doe";
}
}
Em vez de registrar a PersonService classe diretamente, você pode registrá-la como uma implementação da IPersonService interface:
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 Program.cs difere do exemplo anterior de duas maneiras:
- A
PersonServiceinstância é registrada como uma implementação da interface (em vez deIPersonServiceregistrar aPersonServiceclasse diretamente). - A assinatura do delegado agora espera um
IPersonServiceparâmetro em vez de umPersonServiceparâmetro.
Quando o aplicativo é executado e um cliente solicita a URL raiz, o contêiner de serviço fornece uma instância da PersonService classe porque é registrado como a implementação da IPersonService interface.
Gorjeta
Pense nisso IPersonService como um contrato. Ele define os métodos e propriedades que uma implementação deve ter. O delegado quer uma instância de IPersonService. Ele não se importa com a implementação subjacente, apenas que a instância tem os métodos e 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 simulada da interface para fins de IPersonService teste. Quando você registra a implementação simulada no teste, o contêiner de serviço fornece a implementação simulada para o componente que está sendo testado.
Por exemplo, digamos que, em vez de retornar uma cadeia de caracteres codificada, o GetPersonName método na PersonService classe busca o nome de um banco de dados. Para testar o componente que depende da IPersonService interface, você pode criar uma implementação simulada da IPersonService interface que retorna uma cadeia de caracteres codificada. O componente que está sendo testado não sabe a diferença entre a implementação real e a implementação simulada.
Suponha também que seu aplicativo mapeie um ponto de extremidade da API que retorna uma mensagem de saudação. O ponto de extremidade depende da IPersonService interface para obter o nome da pessoa a cumprimentar. O código que registra o serviço e mapeia IPersonService o ponto de extremidade da API pode ter esta aparência:
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 é semelhante ao exemplo anterior com IPersonService. O delegado espera um IPersonService parâmetro, que o contêiner de serviço fornece. Como mencionado anteriormente, suponha que o PersonService que implementa a interface busca o nome da pessoa para saudar de um banco de dados.
Agora, considere o seguinte teste XUnit que testa o mesmo ponto de extremidade da API:
Gorjeta
Não se preocupe se você não estiver familiarizado com XUnit ou Moq. A escrita de testes de unidade está fora do âmbito deste módulo. Este exemplo é 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 simulada da interface que retorna uma cadeia de
IPersonServicecaracteres codificada. - Registra a implementação simulada com o contêiner de serviço.
- Cria um cliente HTTP para fazer uma solicitação ao ponto de extremidade da API.
- Afirma que a resposta do ponto de extremidade da API é a esperada.
O teste não se importa como a PersonService classe recebe o nome da pessoa para cumprimentar. Ele só se importa que o nome seja incluído na mensagem de saudação. O teste usa uma implementação simulada da IPersonService interface para isolar o componente que está sendo testado da implementação real do serviço.