Vysvětlení injektáže závislostí
ASP.NET aplikace Core často potřebují přístup ke stejným službám napříč několika komponentami. Například několik komponent může potřebovat přístup ke službě, která načítá data z databáze. ASP.NET Core ke správě služeb, které aplikace používá, používá integrovaný kontejner injektáže závislostí (DI).
Injektáž závislostí a inverze řízení (IoC)
Model injektáže závislostí je forma inverze řízení (IoC). V modelu injektáže závislostí přijímá komponenta své závislosti z externích zdrojů, nikoli z jejich samotného vytváření. Tento vzor odděluje kód od závislosti, což usnadňuje testování a údržbu kódu.
Zvažte následující Program.cs soubor:
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();
A následující PersonService.cs soubor:
namespace MyApp.Services;
public class PersonService
{
public string GetPersonName()
{
return "John Doe";
}
}
Abyste pochopili kód, začněte zvýrazněným app.MapGet kódem. Tento kód mapuje požadavky HTTP GET pro kořenovou adresu URL (/) na delegáta, který vrací zprávu s pozdravem. Podpis delegáta PersonService definuje parametr s názvem personService. Když se aplikace spustí a klient požádá o kořenovou adresu URL, kód uvnitř delegáta závisí na PersonService službu, aby získal určitý text, který se má zahrnout do zprávy s pozdravem.
Kde delegát službu získá PersonService ? Kontejner služby ho implicitně poskytuje. Zvýrazněný builder.Services.AddSingleton<PersonService>() řádek říká kontejneru služby, aby při spuštění aplikace vytvořil novou instanci PersonService třídy a poskytl ji libovolné komponentě, která ji potřebuje.
Každá komponenta, která službu potřebuje PersonService , může deklarovat parametr typu PersonService v podpisu delegáta. Kontejner služby automaticky poskytne instanci PersonService třídy při vytvoření komponenty. Delegát nevytvoří PersonService samotnou instanci, pouze používá instanci, kterou poskytuje kontejner služby.
Injektáž rozhraní a závislostí
Abyste se vyhnuli závislostem na konkrétní implementaci služby, můžete místo toho nakonfigurovat službu pro konkrétní rozhraní a pak záviset jen na rozhraní. Tento přístup vám dává flexibilitu prohodit implementaci služby, což usnadňuje testování kódu a usnadňuje údržbu.
Zvažte rozhraní pro PersonService třídu:
public interface IPersonService
{
string GetPersonName();
}
Toto rozhraní definuje jednu metodu, GetPersonNamekterá vrací string. Tato PersonService třída implementuje IPersonService rozhraní:
internal sealed class PersonService : IPersonService
{
public string GetPersonName()
{
return "John Doe";
}
}
Místo přímé registrace PersonService třídy ji můžete zaregistrovat jako implementaci IPersonService rozhraní:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IPersonService, PersonService>();
var app = builder.Build();
app.MapGet("/",
(IPersonService personService) =>
{
return $"Hello, {personService.GetPersonName()}!";
}
);
app.Run();
Tento příklad Program.cs se liší od předchozího příkladu dvěma způsoby:
- Instance
PersonServiceje registrována jako implementaceIPersonServicerozhraní (na rozdíl od registracePersonServicetřídy přímo). - Podpis delegáta
IPersonServiceteď místo parametruPersonServiceočekává parametr.
Když aplikace spustí a klient požádá o kořenovou adresu URL, kontejner služby poskytne instanci PersonService třídy, protože je zaregistrovaná jako implementace IPersonService rozhraní.
Návod
IPersonService Představte si smlouvu. Definuje metody a vlastnosti, které musí mít implementace. Delegát chce instanci IPersonService. Nezáleží vůbec na základní implementaci, pouze to, že instance má metody a vlastnosti definované v kontraktu.
Testování pomocí injektáže závislostí
Použití rozhraní usnadňuje testování komponent v izolaci. Pro účely testování můžete vytvořit napodobenou implementaci IPersonService rozhraní. Když v testu zaregistrujete implementaci napodobení, kontejner služby poskytne napodobenou implementaci komponenty, která se testuje.
Řekněme například, že místo vrácení pevně zakódovaného řetězce GetPersonName metoda ve PersonService třídě načte název z databáze. Pokud chcete otestovat komponentu, která závisí na IPersonService rozhraní, můžete vytvořit napodobenou implementaci IPersonService rozhraní, která vrátí pevně zakódovaný řetězec. Testovaná komponenta nezná rozdíl mezi skutečnou implementací a implementací napodobení.
Předpokládejme také, že vaše aplikace mapuje koncový bod rozhraní API, který vrací zprávu s pozdravem. Koncový bod závisí na rozhraní pro IPersonService získání jména osoby, která se má pozdravit. Kód, který zaregistruje IPersonService službu a mapuje koncový bod rozhraní API, může vypadat takto:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IPersonService, PersonService>();
var app = builder.Build();
app.MapGet("/", (IPersonService personService) =>
{
return $"Hello, {personService.GetPersonName()}!";
});
app.Run();
Toto je podobné předchozímu příkladu s IPersonService. Delegát očekává IPersonService parametr, který kontejner služby poskytuje. Jak už jsme zmínili dříve, předpokládejme, že PersonService implementuje rozhraní, načte jméno osoby, která má pozdravit z databáze.
Teď zvažte následující test XUnit, který testuje stejný koncový bod rozhraní API:
Návod
Nemějte obavy, pokud neznáte XUnit nebo Moq. Psaní testů jednotek je mimo rozsah tohoto modulu. Tento příklad ukazuje, jak lze injektáž závislostí použít při testování.
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);
}
}
Předchozí test:
- Vytvoří napodobenou implementaci
IPersonServicerozhraní, která vrátí pevně zakódovaný řetězec. - Zaregistruje implementaci napodobení v kontejneru služby.
- Vytvoří klienta HTTP, který vytvoří požadavek na koncový bod rozhraní API.
- Tvrdí, že odpověď z koncového bodu rozhraní API je podle očekávání.
Test nezajímá, jak PersonService třída získá jméno osoby, která se má pozdravit. Záleží jenom na tom, že jméno je součástí zprávy s pozdravem. Test používá napodobení implementace IPersonService rozhraní k izolaci komponenty testované od skutečné implementace služby.