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 sealed
sein, 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 ExampleSingletonService
IExampleSingletonService
.
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
, IExampleScopedService
und 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 klassenProgram
oderStartup
, in denen diese Erweiterungsmethoden normalerweise aufgerufen werden.
Die App:
- Erstellt eine IHostBuilder Instanz mit Host-Generator-Einstellungen.
- Konfiguriert Dienste und fügt sie mit der entsprechenden Dienstlebensdauer hinzu.
- Sie ruft Build() auf und weist eine Instanz von IHost zu.
- Sie ruft
ExemplifyScoping
auf und übergibt dabei IHost.Services.
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.