Teilen über


Lernprogramm: Verwenden der Abhängigkeitsinjektion in .NET

In diesem Tutorial erfahren Sie, wie die Dependency Injection (DI) in .NET eingesetzt wird. Die Abhängigkeitsinjektion (Dependency Injection, DI) wird mit Microsoft-Erweiterungen verwaltet, indem Dienste hinzugefügt und über eine IServiceCollection-Schnittstelle konfiguriert werden. Die IHost Schnittstelle macht die IServiceProvider Instanz verfügbar, die als Container aller registrierten Dienste fungiert.

In diesem Lernprogramm erfahren Sie, wie Sie:

  • Erstellen einer .NET-Konsolen-App, die Abhängigkeitsinjektion verwendet
  • Erstellen und Konfigurieren eines generischen Hosts
  • Schreiben mehrerer Schnittstellen und entsprechender Implementierungen
  • Verwenden der Dienstlebensdauer und der Bereichsdefinitionen für die DI

Voraussetzungen

  • SDK für .NET Core 3.1 oder höher
  • Vertrautheit mit dem Erstellen neuer .NET-Anwendungen und Installieren von NuGet-Paketen.

Erstellen einer neuen Konsolenanwendung

Erstellen Sie über den Befehl dotnet new oder den Assistenten für neue Projekte auf der IDE eine neue .NET-Konsolenanwendung namens ConsoleDI.Example . Fügen Sie das Microsoft.Extensions.Hosting NuGet-Paket zum Projekt hinzu.

Ihre neue Konsolen-App-Projektdatei sollte wie folgt aussehen:

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

</Project>

Wichtig

In diesem Beispiel ist das Microsoft.Extensions.Hosting NuGet-Paket erforderlich, um die App zu erstellen und auszuführen. Einige Metapackages können das Microsoft.Extensions.Hosting-Paket enthalten, in diesem Fall ist kein expliziter Paketverweis erforderlich.

Hinzufügen von Schnittstellen

In dieser Beispielanwendung erfahren Sie, wie Dependency Injection die Lebensdauer von Diensten verwaltet. Sie erstellen mehrere Schnittstellen, die unterschiedliche Dienstlebensdauern darstellen. Fügen Sie dem Projektstammverzeichnis die folgenden Schnittstellen hinzu:

IReportServiceLifetime.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IReportServiceLifetime
{
    Guid Id { get; }

    ServiceLifetime Lifetime { get; }
}

Die IReportServiceLifetime-Schnittstelle definiert Folgendes:

  • Eine Guid Id-Eigenschaft, die den eindeutigen Bezeichner des Diensts darstellt.
  • ServiceLifetime-Eigenschaft, die die Dienstlebensdauer darstellt

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

Alle Unterinterfaces von IReportServiceLifetime implementieren IReportServiceLifetime.Lifetime explizit mit einer Standardeinstellung. IExampleTransientService implementiert beispielsweise IReportServiceLifetime.Lifetime explizit mit dem ServiceLifetime.Transient-Wert.

Hinzufügen von Standardimplementierungen

Die Beispielimplementierungen initialisieren ihre Id-Eigenschaft mit dem Ergebnis von Guid.NewGuid(). Fügen Sie die folgenden Standardimplementierungsklassen für die verschiedenen Dienste zum Projektstammverzeichnis hinzu:

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

Jede Implementierung wird als internal sealed definiert und implementiert die entsprechende Schnittstelle. Sie müssen nicht internal oder sealedsein, es ist jedoch üblich, die Implementierungen als internal zu deklarieren, um zu verhindern, dass Implementierungstypen an externe Verbraucher weitergegeben werden. Da außerdem nicht jeder Typ erweitert wird, wird er als sealed markiert. Beispielsweise implementiert ExampleSingletonServiceIExampleSingletonService.

Hinzufügen eines Diensts, der DI erfordert

Fügen Sie der Konsolen-App die folgende Dienstlebensdauer-Reporterklasse hinzu, die als Dienst fungiert:

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

Die ServiceLifetimeReporter definiert einen Konstruktor, der jede der oben genannten Dienstschnittstellen erfordert, d. h. IExampleTransientService, IExampleScopedServiceund IExampleSingletonService. Das Objekt macht eine einzelne Methode verfügbar, mit der der Verbraucher den Dienst mit einem bestimmten lifetimeDetails-Parameter melden kann. Wenn sie aufgerufen wird, protokolliert die ReportServiceLifetimeDetails-Methode den eindeutigen Bezeichner jedes Diensts mit der Nachricht für die Dienstlebensdauer. Die Protokollmeldungen helfen beim Visualisieren der Dienstlebensdauer.

Registrieren von Diensten für die DI

Aktualisieren Sie Program.cs mit dem folgenden Code:

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

Jede services.Add{LIFETIME}<{SERVICE}> Erweiterungsmethode fügt Dienste hinzu (und konfiguriert potenziell). Es wird empfohlen, dass Apps dieser Konvention folgen. Platzieren Sie Erweiterungsmethoden nicht im Microsoft.Extensions.DependencyInjection-Namespace, es sei denn, Sie erstellen ein offizielles Microsoft-Paket. Erweiterungsmethoden, die im Microsoft.Extensions.DependencyInjection-Namespace definiert sind:

  • Werden in IntelliSense ohne zusätzliche using Direktiven angezeigt.
  • Verringern Sie die Anzahl der erforderlichen using Direktiven in den klassen Program oder Startup, in denen diese Erweiterungsmethoden normalerweise aufgerufen werden.

Die App:

Schlussfolgerung

In dieser Beispiel-App haben Sie mehrere Schnittstellen und entsprechende Implementierungen erstellt. Jeder dieser Dienste wird eindeutig identifiziert und mit einer ServiceLifetime-Enumeration gekoppelt. Die Beispiel-App veranschaulicht das Registrieren von Dienstimplementierungen für eine Schnittstelle und das Registrieren von eigenständigen Klassen ohne zugehörige Schnittstellen. Die Beispiel-App veranschaulicht dann, wie Abhängigkeiten, die als Konstruktorparameter definiert sind, zur Laufzeit aufgelöst werden.

Wenn Sie die App ausführen, wird die Ausgabe ähnlich wie folgt angezeigt:

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

Aus der App-Ausgabe können Sie folgendes sehen:

  • Transient-Dienste sind immer unterschiedlich; bei jedem Abruf des Dienstes wird eine neue Instanz erstellt.
  • Scoped Dienste ändern sich nur mit einem neuen Bereich, sind aber dieselbe Instanz innerhalb eines Bereichs.
  • Singleton-Dienste sind immer gleich, eine neue Instanz wird nur einmal erstellt.

Siehe auch