Samouczek: używanie wstrzykiwania zależności na platformie .NET
W tym samouczku pokazano, jak używać wstrzykiwania zależności (DI) na platformie .NET. W przypadku rozszerzeń firmy Microsoft di jest zarządzane przez dodawanie usług i konfigurowanie ich w programie IServiceCollection. Interfejs IHost uwidacznia IServiceProvider wystąpienie, które działa jako kontener wszystkich zarejestrowanych usług.
Z tego samouczka dowiesz się, jak wykonywać następujące czynności:
- Tworzenie aplikacji konsolowej platformy .NET korzystającej z wstrzykiwania zależności
- Kompilowanie i konfigurowanie hosta ogólnego
- Pisanie kilku interfejsów i odpowiednich implementacji
- Używanie okresu istnienia usługi i określania zakresu dla di
Wymagania wstępne
- Zestaw .NET Core 3.1 SDK lub nowszy.
- Znajomość tworzenia nowych aplikacji .NET i instalowania pakietów NuGet.
Tworzenie nowej aplikacji konsolowej
Za pomocą polecenia dotnet new lub kreatora nowego projektu IDE utwórz nową aplikację konsolową platformy .NET o nazwie ConsoleDI.Example Dodaj pakiet NuGet Microsoft.Extensions.Hosting do projektu.
Nowy plik projektu aplikacji konsolowej powinien wyglądać podobnie do następującego:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<RootNamespace>ConsoleDI.Example</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>
</Project>
Ważne
W tym przykładzie pakiet NuGet Microsoft.Extensions.Hosting jest wymagany do skompilowania i uruchomienia aplikacji. Niektóre metapakiet mogą zawierać Microsoft.Extensions.Hosting
pakiet, w tym przypadku jawne odwołanie do pakietu nie jest wymagane.
Dodawanie interfejsów
W tej przykładowej aplikacji dowiesz się, jak wstrzykiwanie zależności obsługuje okres istnienia usługi. Utworzysz kilka interfejsów reprezentujących różne okresy istnienia usługi. Dodaj następujące interfejsy do katalogu głównego projektu:
IReportServiceLifetime.cs
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IReportServiceLifetime
{
Guid Id { get; }
ServiceLifetime Lifetime { get; }
}
Interfejs IReportServiceLifetime
definiuje:
- Właściwość
Guid Id
reprezentująca unikatowy identyfikator usługi. - Właściwość ServiceLifetime reprezentująca okres istnienia usługi.
Service.csExampleTransient
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IExampleTransientService : IReportServiceLifetime
{
ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Transient;
}
Service.csExampleScoped
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IExampleScopedService : IReportServiceLifetime
{
ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Scoped;
}
Service.csExampleSingleton
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IExampleSingletonService : IReportServiceLifetime
{
ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}
Wszystkie podpowierzchniki IReportServiceLifetime
jawnie implementują IReportServiceLifetime.Lifetime
element z wartością domyślną. Na przykład IExampleTransientService
jawnie implementuje IReportServiceLifetime.Lifetime
wartość ServiceLifetime.Transient
.
Dodawanie implementacji domyślnych
W przykładzie wszystkie implementacje inicjują ich Id
właściwość z wynikiem .Guid.NewGuid() Dodaj następujące domyślne klasy implementacji dla różnych usług do katalogu głównego projektu:
ExampleTransientService.cs
namespace ConsoleDI.Example;
internal sealed class ExampleTransientService : IExampleTransientService
{
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}
ExampleScopedService.cs
namespace ConsoleDI.Example;
internal sealed class ExampleScopedService : IExampleScopedService
{
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}
ExampleSingletonService.cs
namespace ConsoleDI.Example;
internal sealed class ExampleSingletonService : IExampleSingletonService
{
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}
Każda implementacja jest definiowana jako internal sealed
i implementuje odpowiedni interfejs. Na przykład ExampleSingletonService
implementuje IExampleSingletonService
wartość .
Dodawanie usługi wymagającej di
Dodaj następującą klasę reportera okresu istnienia usługi, która działa jako usługa w aplikacji konsolowej:
ServiceLifetimeReporter.cs
namespace ConsoleDI.Example;
internal sealed class ServiceLifetimeReporter(
IExampleTransientService transientService,
IExampleScopedService scopedService,
IExampleSingletonService singletonService)
{
public void ReportServiceLifetimeDetails(string lifetimeDetails)
{
Console.WriteLine(lifetimeDetails);
LogService(transientService, "Always different");
LogService(scopedService, "Changes only with lifetime");
LogService(singletonService, "Always the same");
}
private static void LogService<T>(T service, string message)
where T : IReportServiceLifetime =>
Console.WriteLine(
$" {typeof(T).Name}: {service.Id} ({message})");
}
Definiuje ServiceLifetimeReporter
konstruktor, który wymaga każdego z wyżej wymienionych interfejsów usług, IExampleTransientService
czyli , IExampleScopedService
, i IExampleSingletonService
. Obiekt uwidacznia pojedynczą metodę, która umożliwia użytkownikowi raportowanie w usłudze przy użyciu danego lifetimeDetails
parametru. Po wywołaniu ReportServiceLifetimeDetails
metoda rejestruje unikatowy identyfikator każdej usługi z komunikatem o okresie istnienia usługi. Komunikaty dziennika ułatwiają wizualizowanie okresu istnienia usługi.
Rejestrowanie usług dla di
Zaktualizuj Program.cs przy użyciu następującego kodu:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ConsoleDI.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddTransient<IExampleTransientService, ExampleTransientService>();
builder.Services.AddScoped<IExampleScopedService, ExampleScopedService>();
builder.Services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
builder.Services.AddTransient<ServiceLifetimeReporter>();
using IHost host = builder.Build();
ExemplifyServiceLifetime(host.Services, "Lifetime 1");
ExemplifyServiceLifetime(host.Services, "Lifetime 2");
await host.RunAsync();
static void ExemplifyServiceLifetime(IServiceProvider hostProvider, string lifetime)
{
using IServiceScope serviceScope = hostProvider.CreateScope();
IServiceProvider provider = serviceScope.ServiceProvider;
ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
logger.ReportServiceLifetimeDetails(
$"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");
Console.WriteLine("...");
logger = provider.GetRequiredService<ServiceLifetimeReporter>();
logger.ReportServiceLifetimeDetails(
$"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");
Console.WriteLine();
}
Każda services.Add{LIFETIME}<{SERVICE}>
metoda rozszerzenia dodaje (i potencjalnie konfiguruje) usługi. Zalecamy, aby aplikacje przestrzegały tej konwencji. Nie umieszczaj metod rozszerzeń w Microsoft.Extensions.DependencyInjection przestrzeni nazw, chyba że tworzysz oficjalny pakiet firmy Microsoft. Metody rozszerzeń zdefiniowane w Microsoft.Extensions.DependencyInjection
przestrzeni nazw:
- Są wyświetlane w funkcji IntelliSense bez konieczności stosowania dodatkowych
using
bloków. - Zmniejsz liczbę wymaganych
using
instrukcji wProgram
klasach lubStartup
, w których te metody rozszerzenia są zwykle wywoływane.
Aplikacja:
- IHostBuilder Tworzy wystąpienie z ustawieniami konstruktora hostów.
- Konfiguruje usługi i dodaje je z odpowiednim okresem istnienia usługi.
- Wywołuje Build() i przypisuje wystąpienie klasy IHost.
- Wywołuje
ExemplifyScoping
metodę , przekazując element IHost.Services.
Podsumowanie
W tej przykładowej aplikacji utworzono kilka interfejsów i odpowiednich implementacji. Każda z tych usług jest jednoznacznie identyfikowana i sparowana z elementem ServiceLifetime. Przykładowa aplikacja demonstruje rejestrowanie implementacji usług względem interfejsu oraz sposób rejestrowania czystych klas bez tworzenia kopii zapasowych interfejsów. Przykładowa aplikacja pokazuje następnie, jak zależności zdefiniowane jako parametry konstruktora są rozpoznawane w czasie wykonywania.
Po uruchomieniu aplikacji zostaną wyświetlone dane wyjściowe podobne do następujących:
// Sample output:
// Lifetime 1: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: d08a27fa-87d2-4a06-98d7-2773af886125 (Always different)
// IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 1: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: b43d68fb-2c7b-4a9b-8f02-fc507c164326 (Always different)
// IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
//
// Lifetime 2: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: f3856b59-ab3f-4bbd-876f-7bab0013d392 (Always different)
// IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 2: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: a8015c6a-08cd-4799-9ec3-2f2af9cbbfd2 (Always different)
// IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
Z danych wyjściowych aplikacji można zobaczyć, że:
- Transient usługi są zawsze inne, nowe wystąpienie jest tworzone przy każdym pobieraniu usługi.
- Scoped usługi zmieniają się tylko z nowym zakresem, ale są tym samym wystąpieniem w zakresie.
- Singleton usługi są zawsze takie same, nowe wystąpienie jest tworzone tylko raz.