Udostępnij za pośrednictwem


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

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 IExampleSingletonServicewartość .

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, IExampleTransientServiceczyli , 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 w Program klasach lub Startup , w których te metody rozszerzenia są zwykle wywoływane.

Aplikacja:

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.

Zobacz też