Freigeben über


Grundlegendes zur Dependency Injection in .NET

In diesem Artikel erstellen Sie eine .NET-Konsolen-App, die manuell eine ServiceCollection und entsprechende ServiceProvidererstellt. Sie erfahren, wie Sie Dienste registrieren und mithilfe der Dependency Injection (DI) auflösen. In diesem Artikel wird das NuGet-Paket Microsoft.Extensions.DependencyInjection verwendet, um die Grundlagen von DI in .NET zu veranschaulichen.

Hinweis

In diesem Artikel werden die generischen Hostfeatures nicht genutzt. Eine ausführlichere Anleitung finden Sie unter Verwenden der Dependency Injection in .NET.

Erste Schritte

Erstellen Sie zunächst eine neue .NET-Konsolenanwendung mit dem Namen DI.Basics. Auf einige der am häufigsten verwendeten Ansätze zum Erstellen eines Konsolenprojekts wird in der folgenden Liste verwiesen:

Sie müssen den Paketverweis auf Microsoft.Extensions.DependencyInjection in der Projektdatei hinzufügen. Stellen Sie unabhängig vom Ansatz sicher, dass das Projekt dem folgenden XML der Datei DI.Basics.csproj ähnelt:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
  </ItemGroup>

</Project>

Allgemeine Informationen zur Dependency Injection

Die Dependency Injection ist ein Entwurfsmuster, das es Ihnen ermöglicht, hartcodierte Abhängigkeiten zu entfernen und ihre Anwendung besser zu verwalten und zu testen. DI ist eine Technik zum Erreichen von Inversion of Control (IoC) zwischen Klassen und ihren Abhängigkeiten.

Die Abstraktionen für DI in .NET werden im NuGet-Paket Microsoft.Extensions.DependencyInjection.Abstractions definiert:

  • IServiceCollection: Gibt einen Vertrag für eine Sammlung von Dienstdeskriptoren an.
  • IServiceProvider: Definiert einen Mechanismus zum Abrufen eines Dienstobjekts.
  • ServiceDescriptor: Beschreibt einen Dienst mit seinem Diensttyp, seiner Implementierung und Lebensdauer.

In .NET wird die Dependency Injection (DI) verwaltet, indem Dienste hinzugefügt und über IServiceCollection konfiguriert werden. Nachdem Dienste registriert wurden, wird die IServiceProvider-Instanz durch Aufrufen der Methode BuildServiceProvider erstellt. Der IServiceProvider dient als Container aller registrierten Dienste und wird verwendet, um Dienste aufzulösen.

Erstellen der example-Dienste

Nicht alle Dienste werden gleich erstellt. Einige Dienste erfordern jedes Mal eine neue Instanz, wenn der Dienstcontainer sie abruft (transient), während andere über Anforderungen (scoped) oder für die gesamte Lebensdauer der App (singleton) freigegeben werden sollten. Weitere Informationen zur Lebensdauer von Diensten finden Sie unter Dienstlebensdauern.

Ebenso machen einige Dienste nur einen konkreten Typ verfügbar, während andere als Vertrag zwischen einer Schnittstelle und einem Implementierungstyp ausgedrückt werden. Sie erstellen mehrere Varianten von Diensten, um diese Konzepte zu veranschaulichen.

Erstellen Sie eine neue C#-Datei mit dem Namen IConsole.cs, und fügen Sie den folgenden Code ein:

public interface IConsole
{
    void WriteLine(string message);
}

Diese Datei definiert eine IConsole-Schnittstelle, die eine einzelne Methode – WriteLine – verfügbar macht. Erstellen Sie als Nächstes eine neue C#-Datei mit dem Namen DefaultConsole.cs, und fügen Sie den folgenden Code ein:

internal sealed class DefaultConsole : IConsole
{
    public bool IsEnabled { get; set; } = true;

    void IConsole.WriteLine(string message)
    {
        if (IsEnabled is false)
        {
            return;
        }

        Console.WriteLine(message);
    }
}

Der vorangehende Code stellt die Standardimplementierung der Schnittstelle IConsole dar. Die Methode WriteLine schreibt bedingt auf der Grundlage der Eigenschaft IsEnabled in die Konsole.

Tipp

Die Benennung einer Implementierung ist eine Wahl, über die sich Ihr Entwicklerteam einigen sollte. Das Präfix Default ist eine gängige Konvention, um eine Standardimplementierung einer Schnittstelle anzugeben, ist jedoch nicht erforderlich.

Erstellen Sie als Nächstes eine IGreetingService.cs-Datei, und fügen Sie den folgenden C#-Code hinzu:

public interface IGreetingService
{
    string Greet(string name);
}

Fügen Sie dann eine neue C#-Datei mit dem Namen DefaultGreetingService.cs und darin den folgenden Code hinzu:

internal sealed class DefaultGreetingService(
    IConsole console) : IGreetingService
{
    public string Greet(string name)
    {
        var greeting = $"Hello, {name}!";

        console.WriteLine(greeting);

        return greeting;
    }
}

Der vorangehende Code stellt die Standardimplementierung der Schnittstelle IGreetingService dar. Für die Dienstimplementierung ist IConsole als primärer Konstruktorparameter erforderlich. Die Greet-Methode:

  • Erstellt eine greeting mit dem name.
  • Ruft die Methode WriteLine für die Instanz IConsole auf.
  • Gibt greeting an den Aufrufer zurück.

Der letzte zu erstellende Dienst ist die Datei FarewellService.cs. Fügen Sie den folgenden C#-Code hinzu, bevor Sie fortfahren:

public class FarewellService(IConsole console)
{
    public string SayGoodbye(string name)
    {
        var farewell = $"Goodbye, {name}!";

        console.WriteLine(farewell);

        return farewell;
    }
}

FarewellService stellt einen konkreten Typ und keine Schnittstelle dar. Er sollte als public deklariert werden, damit er den Verbrauchern zugänglich ist. Im Gegensatz zu anderen Dienstimplementierungstypen, die als internal und sealeddeklariert wurden, veranschaulicht dieser Code, dass nicht alle Dienste Schnittstellen sein müssen. Außerdem wird gezeigt, dass Dienstimplementierungen sealed werden können, um die Vererbung zu verhindern und internal, um den Zugriff auf die Assembly einzuschränken.

Aktualisieren Sie die Klasse Program.

Ersetzen Sie in der Datei Program.cs den vorhandenen Code durch den folgenden C#-Code:

using Microsoft.Extensions.DependencyInjection;

// 1. Create the service collection.
var services = new ServiceCollection();

// 2. Register (add and configure) the services.
services.AddSingleton<IConsole>(
    implementationFactory: static _ => new DefaultConsole
    {
        IsEnabled = true
    });
services.AddSingleton<IGreetingService, DefaultGreetingService>();
services.AddSingleton<FarewellService>();

// 3. Build the service provider from the service collection.
var serviceProvider = services.BuildServiceProvider();

// 4. Resolve the services that you need.
var greetingService = serviceProvider.GetRequiredService<IGreetingService>();
var farewellService = serviceProvider.GetRequiredService<FarewellService>();

// 5. Use the services
var greeting = greetingService.Greet("David");
var farewell = farewellService.SayGoodbye("David");

Im vorangehenden aktualisierten Code wird die Vorgehensweise veranschaulicht:

  • Erstellen Sie eine neue ServiceCollection-Instanz.
  • Registrieren und Konfigurieren von Diensten in der ServiceCollection:
    • Geben Sie IConsole mit der Implementierungsfactoryüberladung einen DefaultConsole-Typ zurück, der IsEnabled auf „true“ festgelegt ist.
    • Der IGreetingService wird mit einem entsprechenden Implementierungstyp des Typs DefaultGreetingService hinzugefügt.
    • Der FarewellService wird als konkreter Typ hinzugefügt.
  • Erstellen Sie den ServiceProvider aus der ServiceCollection.
  • Lösen Sie die Dienste IGreetingService und FarewellService auf.
  • Verwenden Sie die aufgelösten Dienste, um eine Person namens David zu begrüßen und zu verabschieden.

Wenn Sie die Eigenschaft IsEnabled von DefaultConsole auf false aktualisieren, unterlassen die Methoden Greet und SayGoodbye das Schreiben der daraus resultierenden Nachrichten in die Konsole. Eine solche Änderung zeigt, dass der Dienst IConsole in die Dienste IGreetingService und FarewellService als Abhängigkeit eingebunden ist, die das Verhalten dieser Anwendungen beeinflusst.

Alle diese Dienste werden als Singletons registriert, obwohl sie für dieses Beispiel identisch funktionieren, wenn sie als transient oder scoped Dienste registriert wurden.

Wichtig

In diesem example spielen die Lebensdauern der Dienste keine Rolle, aber in einer realen Anwendung sollten Sie die Lebensdauer jedes Dienstes sorgfältig prüfen.

Ausführen der Beispiel-App

Um die Beispielanwendung auszuführen, drücken Sie entweder F5 in Visual Studio, Visual Studio Code oder führen Sie den Befehl dotnet run im Terminal aus. Wenn die Anwendung abgeschlossen ist, sollten Sie die folgende Ausgabe sehen:

Hello, David!
Goodbye, David!

Dienstdeskriptoren

Die am häufigsten verwendeten APIs zum Hinzufügen von Diensten zum ServiceCollection sind generische Erweiterungsmethoden mit Lebenszeitnamen, wie z. B.:

  • AddSingleton<TService>
  • AddTransient<TService>
  • AddScoped<TService>

Bei diesen Methoden handelt es sich um Komfortmethoden, die eine ServiceDescriptor-Instanz erstellen und sie zu ServiceCollection hinzufügen. Der ServiceDescriptor ist eine einfache Klasse, die einen Dienst mit seinem Diensttyp, seiner Implementierungsart und seiner Lebensdauer beschreibt. Er kann auch Implementierungsfabriken und Instanzen beschreiben.

Für jeden der Dienste, die Sie in ServiceCollection registriert haben, könnten Sie stattdessen die Methode Add mit einer ServiceDescriptor-Instanz direkt aufrufen. Betrachten Sie die folgenden Beispiele:

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(IConsole),
    implementationFactory: static _ => new DefaultConsole
    {
        IsEnabled = true
    },
    lifetime: ServiceLifetime.Singleton));

Der vorangehende Code entspricht der Registrierung des Diensts IConsole in der ServiceCollection. Die Methode Add wird verwendet, um eine ServiceDescriptor-Instanz hinzuzufügen, die den Dienst IConsole beschreibt. Die statische Methode ServiceDescriptor.Describe delegiert an verschiedene ServiceDescriptor-Konstruktoren. Berücksichtigen Sie den entsprechenden Code für den Dienst IGreetingService:

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(IGreetingService),
    implementationType: typeof(DefaultGreetingService),
    lifetime: ServiceLifetime.Singleton));

Der vorangehende Code beschreibt den Dienst IGreetingService mit dem Diensttyp, dem Implementierungstyp und der Lebensdauer. Berücksichtigen Sie abschließend den entsprechenden Code für den Dienst FarewellService:

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(FarewellService),
    implementationType: typeof(FarewellService),
    lifetime: ServiceLifetime.Singleton));

Der vorangehende Code beschreibt den konkreten Typ FarewellService sowohl als Dienst- als auch als Implementierungstypen. Der Dienst wird als singleton-Dienst registriert.

Siehe auch