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

V tomto kurzu se dozvíte, jak používat injektáž závislostí (DI) v .NET. Pomocí rozšíření Microsoftu se DI spravuje přidáním služeb a jejich konfigurací v objektu IServiceCollection. Rozhraní IHost zveřejňuje IServiceProvider instanci, 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í
  • Sestavení a konfigurace obecného hostitele
  • Zápis několika rozhraní a odpovídajících implementací
  • 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 průvodce novým projektem IDE vytvořte novou konzolovou aplikaci .NET s názvem ConsoleDI.Example. Přidejte do projektu balíček NuGet Microsoft.Extensions.Hosting .

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.Hosting" Version="8.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 balíček obsahovat 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 injektáž závislostí zpracovává životnost 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.

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

Všechny podinterfaces IReportServiceLifetime explicitně implementují IReportServiceLifetime.Lifetime s výchozím nastavením. Například IExampleTransientService explicitně implementuje IReportServiceLifetime.Lifetime s ServiceLifetime.Transient hodnotou.

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

Příklad implementace všechny inicializovat své Id vlastnosti 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 své odpovídající rozhraní. Například ExampleSingletonService implementuje IExampleSingletonService.

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

Přidejte do konzolové aplikace následující třídu reporteru doby životnosti služby, která funguje jako služba:

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

Definuje ServiceLifetimeReporter konstruktor, který vyžaduje všechna výše uvedená rozhraní služby, tj. IExampleTransientService, , IExampleScopedServicea IExampleSingletonService. Objekt zveřejňuje jednu metodu, která příjemci umožňuje hlásit službu s daným lifetimeDetails parametrem. Při vyvolání ReportServiceLifetimeDetails metoda zaznamená 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á services.Add{LIFETIME}<{SERVICE}> metoda rozšíření přidává (a potenciálně konfiguruje) služby. Doporučujeme, aby aplikace dodržovaly tuto konvenci. Metody rozšíření neumisťujte do Microsoft.Extensions.DependencyInjection oboru názvů, pokud nevytváření oficiálního balíčku Microsoftu. Rozšiřující metody definované v rámci Microsoft.Extensions.DependencyInjection oboru názvů:

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

Aplikace:

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 přeloží za 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é