Freigeben über


Schnellstart: Grundlagen der Abhängigkeitsinjektion in .NET

In dieser Schnellstartanleitung erstellen Sie eine .NET-Konsolen-App, die manuell eine ServiceCollection und ein entsprechendes ServiceProvider erstellt. Sie lernen, wie Sie mithilfe der Abhängigkeitsinjektion (Dependency Injection, DI) Dienste registrieren und auflösen. In diesem Artikel wird das NuGet-Paket "Microsoft.Extensions.DependencyInjection NuGet" 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 Abhängigkeitsinjektion in .NET.

Get started

Erstellen Sie zunächst eine neue .NET-Konsolenanwendung mit dem Namen DI.Basics. Wählen Sie in Visual Studio Datei > Neu > Projekt, oder geben Sie mithilfe der .NET CLIdotnet new console ein.

Fügen Sie als Nächstes einen Paketverweis auf microsoft.Extensions.DependencyInjection in der Projektdatei hinzu. Stellen Sie nach dem Hinzufügen des Pakets sicher, dass das Projekt dem folgenden XML der DATEI DI.Basics.csproj ähnelt:

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

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

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

</Project>

Allgemeine Informationen zur Abhängigkeitsinjektion

Die Abhängigkeitseinfügung ist ein Entwurfsmuster, das Sie verwenden können, um 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.

Das NuGet-Paket "Microsoft.Extensions.DependencyInjection.Abstractions NuGet" definiert die Abstraktionen für DI in .NET:

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

In .NET verwalten Sie DI, indem Sie Dienste hinzufügen und in einer IServiceCollection. Rufen Sie nach dem Registrieren von Diensten die BuildServiceProvider Methode auf, um eine IServiceProvider Instanz zu erstellen. Der IServiceProvider Dient als Container für alle registrierten Dienste, und Sie verwenden ihn, um Dienste aufzulösen.

Erstellen von Beispieldiensten

Nicht alle Dienste sind gleich. Einige Dienste erfordern jedes Mal eine neue Instanz, wenn der Dienstcontainer sie erhält (transient), während andere für einzelne Anfragen (Scoped) oder für die gesamte Lebensdauer der App (Singleton) bereitgestellt werden sollten. Weitere Informationen zu Dienstlebensdauern finden Sie unter Dienstlebensdauer.

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 hinzu:

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

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

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 IConsole Schnittstelle dar. Die Methode WriteLine schreibt bedingt in die Konsole IsEnabled basierend auf der Eigenschaft.

Tipp

Die Benennung einer Implementierung ist eine Wahl, mit der sich Ihr Entwicklerteam einigen sollte. Das Default Präfix 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 namens DefaultGreetingService.cs hinzu , und fügen Sie 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 IGreetingService Schnittstelle dar. Für die Dienstimplementierung ist ein IConsole primärer Konstruktorparameter erforderlich. Die Greet-Methode:

  • Erstellt einen greeting, wenn der name gegeben ist.
  • Ruft die WriteLine Methode für die IConsole Instanz auf.
  • Gibt greeting an den Aufrufer zurück.

Die DefaultGreetingService Klasse zeigt, dass Sie seal Dienstimplementierungen verwenden können, um die Vererbung zu verhindern, und internal den Zugriff auf die Assembly einschränken.

Der letzte zu erstellende Dienst ist die FarewellService.cs Datei. 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;
    }
}

Dies FarewellService stellt einen konkreten Typ dar, keine Schnittstelle. Sie sollten sie public deklarieren, um sie für Verbraucher zugänglich zu machen. Im Gegensatz zu anderen Dienstimplementierungstypen, die Sie als internal und sealed deklarieren, veranschaulicht dieser Code, dass nicht alle Dienste Schnittstellen sein müssen.

Aktualisieren der Program Klasse

Öffnen Sie die Program.cs Datei, und ersetzen Sie 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 Code wird veranschaulicht, wie:

  • Erstellen Sie eine neue ServiceCollection Instanz.
  • Registrieren und Konfigurieren von Diensten im ServiceCollection:
    • Der IConsole Dienst mithilfe der Implementierungs-Factory-Überladung. Gibt einen DefaultConsole Typ zurück, bei dem die Eigenschaft IsEnabled auf true festgelegt ist.
    • Der IGreetingService Dienst mit einem entsprechenden Implementierungstyp von DefaultGreetingService.
    • Der FarewellService Dienst als konkreter Typ.
  • Erstellen Sie das ServiceProvider aus dem ServiceCollection.
  • Lösen Sie die IGreetingService- und FarewellService-Dienste auf.
  • Verwenden Sie die aufgelösten Dienste, um eine Person namens Davidzu begrüßen und zu verabschieden.

Wenn Sie die IsEnabled-Eigenschaft des DefaultConsole auf false aktualisieren, lassen die Greet- und SayGoodbye-Methoden das Schreiben der resultierenden Meldungen auf die Konsole aus. Diese Änderung verdeutlicht, dass der IConsole Dienst in die IGreetingService und FarewellService Dienste als Abhängigkeit injiziert wird, was das Verhalten der App beeinflusst.

Alle diese Dienste werden als Singletons registriert. Für dieses Beispiel funktioniert es identisch, wenn Sie sie als vorübergehende oder bereichsbezogene Dienste registrieren.

Von Bedeutung

In diesem künstlichen Beispiel spielen die Dienstlebensdauern keine Rolle. Berücksichtigen Sie in einer realen Anwendung sorgfältig die Lebensdauer der einzelnen Dienste.

Führen Sie die Beispielanwendung aus

Um die Beispiel-App auszuführen, drücken Sie F5 in Visual Studio oder Visual Studio Code, oder führen Sie den dotnet run Befehl im Terminal aus. Nachdem die App abgeschlossen ist, wird das folgende Ergebnis angezeigt:

Hello, David!
Goodbye, David!

Dienstdeskriptoren

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

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

Diese Methoden sind Komfortmethoden, die eine ServiceDescriptor Instanz erstellen und sie zu der ServiceCollection hinzufügen. Dies ServiceDescriptor ist eine einfache Klasse, die einen Dienst mit seinem Diensttyp, Implementierungstyp und Lebensdauer beschreibt. Es kann auch Implementierungsfabriken und Instanzen beschreiben.

Für jeden Dienst, den Sie im ServiceCollection registriert haben, können Sie stattdessen die Add-Methode direkt mit einer ServiceDescriptor-Instanz 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 IConsole Diensts in der ServiceCollection. Die Add Methode fügt eine ServiceDescriptor Instanz hinzu, die den IConsole Dienst beschreibt. Die statische Methode ServiceDescriptor.Describe delegiert an verschiedene ServiceDescriptor Konstruktoren. Berücksichtigen Sie den entsprechenden Code für den IGreetingService Dienst:

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

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

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

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

Siehe auch