Sdílet prostřednictvím


Kurz: Použití injektáže závislostí v .NET

V tomto kurzu se dozvíte, jak používat injektáž závislostí (DI) v rozhraní .NET. Pomocí rozšíření Microsoft Extensions se DI spravuje přidáním služeb a jejich konfigurací v IServiceCollection. Rozhraní IHost zveřejňuje instanci IServiceProvider, která funguje jako kontejner všech registrovaných služeb.

V tomto kurzu se naučíte:

  • Vytvoření konzolové aplikace .NET, která používá injektáž závislostí
  • Konfigurace a sestavení obecného hostitele
  • Napište několik rozhraní a jejich odpovídající implementace
  • Použití doby životnosti a rozsahu služby pro DI

Požadavky

  • .NET Core 3.1 SDK nebo novější.
  • Znalost vytváření nových aplikací .NET a instalace balíčků NuGet

Vytvoření nové konzolové aplikace

Pomocí příkazu dotnet new nebo pomocí průvodce novým projektem integrovaného vývojového prostředí (IDE) vytvořte novou konzolovou aplikaci .NET s názvem ConsoleDI.Example. Přidejte balíček NuGet Microsoft.Extensions.Hosting do projektu.

Nový soubor projektu aplikace konzoly by měl vypadat přibližně takto:

<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.Configuration.Binder" Version="10.0.0" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
  </ItemGroup>

</Project>

Důležitý

V tomto příkladu se k sestavení a spuštění aplikace vyžaduje balíček NuGet Microsoft.Extensions.Hosting. Některé metabalíky můžou obsahovat balíček Microsoft.Extensions.Hosting, v takovém případě se nevyžaduje explicitní odkaz na balíček.

Přidání rozhraní

V této ukázkové aplikaci se dozvíte, jak dependency injection zpracovává životní cyklus služby. Vytvoříte několik rozhraní, která představují různé životnosti služeb. Do kořenového adresáře projektu přidejte následující rozhraní:

IReportServiceLifetime.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IReportServiceLifetime
{
    Guid Id { get; }

    ServiceLifetime Lifetime { get; }
}

Rozhraní IReportServiceLifetime definuje:

  • Vlastnost Guid Id, která představuje jedinečný identifikátor služby.
  • Vlastnost ServiceLifetime, která představuje životnost služby.

IExampleTransientService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleTransientService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Transient;
}

IExampleScopedService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleScopedService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Scoped;
}

IExampleSingletonService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleSingletonService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}

Všechna podrozhraní IReportServiceLifetime explicitně implementují IReportServiceLifetime.Lifetime s výchozí hodnotou. Například IExampleTransientService explicitně implementuje IReportServiceLifetime.Lifetime s hodnotou ServiceLifetime.Transient.

Přidání výchozích implementací

Příklady implementací všechny inicializují svou vlastnost Id s výsledkem Guid.NewGuid(). Do kořenového adresáře projektu přidejte následující výchozí třídy implementace pro různé služby:

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ždá implementace je definována jako internal sealed a implementuje odpovídající rozhraní. Nemusí být internal ani sealed, je však běžné zacházet s implementacemi jako s internal, aby nedocházelo k úniku typů implementace externím příjemcům. Vzhledem k tomu, že každý typ není rozšířený, je označený jako sealed. Například ExampleSingletonService implementuje IExampleSingletonService.

Přidání služby, která vyžaduje DI

Přidejte následující třídu reportéra životnosti služby, která funguje jako služba, do konzolové aplikace.

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})");
}

ServiceLifetimeReporter definuje konstruktor, který vyžaduje všechna výše uvedená rozhraní služby, tj. IExampleTransientService, IExampleScopedServicea IExampleSingletonService. Objekt poskytuje jednu metodu, která umožňuje uživateli hlásit službu s daným parametrem lifetimeDetails. Při vyvolání protokoluje metoda ReportServiceLifetimeDetails jedinečný identifikátor každé služby se zprávou životnosti služby. Zprávy protokolu pomáhají vizualizovat životnost služby.

Registrace služeb pro DI

Aktualizujte Program.cs následujícím kódem:

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ždá metoda rozšíření services.Add{LIFETIME}<{SERVICE}> přidává (a potenciálně konfiguruje) služby. Doporučujeme, aby aplikace dodržovaly tuto konvenci. Nepoužívejte metody rozšíření v oboru názvů Microsoft.Extensions.DependencyInjection, pokud nevytváříte oficiální balíček od Microsoftu. Rozšiřující metody definované v rámci oboru názvů Microsoft.Extensions.DependencyInjection:

  • Zobrazují se v IntelliSense bez nutnosti dalších using direktiv.
  • Snižte počet požadovaných direktiv using ve třídách Program nebo Startup, kde se tyto rozšiřující metody obvykle volají.

Aplikace:

  • Vytvoří instanci IHostApplicationBuilder s nastaveními hostitelského builderu .
  • Nakonfiguruje služby a přidá je s odpovídající životností služby.
  • Volá Build() a přiřazuje instanci objektu IHost.
  • Volání ExemplifyServiceLifetimea předávání IHost.Services.

Závěr

V této ukázkové aplikaci jste vytvořili několik rozhraní a odpovídající implementace. Každá z těchto služeb je jedinečně identifikována a spárována s ServiceLifetime. Ukázková aplikace demonstruje registraci implementací služby na rozhraní a postup registrace čistých tříd bez rozhraní. Ukázková aplikace pak ukazuje, jak se závislosti definované jako parametry konstruktoru vyřeší v době běhu.

Při spuštění aplikace se zobrazí výstup podobný následujícímu:

// 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)

Ve výstupu aplikace vidíte, že:

  • Transient služby se vždy liší. Vytvoří se nová instance s každým načtením služby.
  • Scoped služby se mění pouze s novým oborem, ale jsou stejnou instancí v rámci oboru.
  • Singleton služby jsou vždy stejné. Nová instance se vytvoří pouze jednou.

Viz také