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 erfahren, wie Sie Dienste registrieren und mithilfe der Abhängigkeitsinjektion (Dependency Injection, DI) 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. 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>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

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

</Project>

Allgemeine Informationen zur Abhängigkeitsinjektion

Die Abhängigkeitseinfügung 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: 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 wird DI verwaltet, indem Dienste hinzugefügt und in einer IServiceCollection konfiguriert werden. Nachdem Dienste registriert wurden, wird eine IServiceProvider Instanz durch Aufrufen der BuildServiceProvider Methode erstellt. Der IServiceProvider Dient als Container aller registrierten Dienste und wird verwendet, 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, über die 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.

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. Es sollte als public deklariert werden, um es für Verbraucher zugänglich zu machen. Im Gegensatz zu anderen Dienstimplementierungstypen, die als internal deklariert wurden und sealeddieser Code veranschaulicht, dass nicht alle Dienste Schnittstellen sein müssen. Außerdem wird gezeigt, dass Dienstimplementierungen die sealed Vererbung verhindern und internal den Zugriff auf die Assembly einschränken können.

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 aktualisierten Code wird die Vorgehensweise veranschaulicht:

  • Erstellen Sie eine neue ServiceCollection Instanz.
  • Registrieren und Konfigurieren von Diensten im ServiceCollection:
    • Verwenden Sie die Implementierungs-Factory-Überladung, um einen DefaultConsole-Typ zurückzugeben, bei dem IsEnabled auf true festgelegt ist.
    • Das IGreetingService wird mit einem entsprechenden Implementierungstyp des Typs DefaultGreetingService hinzugefügt.
    • Das FarewellService wird als konkreter Typ hinzugefügt.
  • 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 in die resultierenden Nachrichten an die Konsole aus. Eine änderung wie dies hilft, zu veranschaulichen, dass der IConsole Dienst in die und IGreetingService dienste als FarewellServiceeingefügt wird, die das Verhalten von Apps beeinflusst.

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

Von Bedeutung

In diesem konstruierten Beispiel spielen die Lebensdauern der Dienste keine Rolle, aber in einer realen Anwendung sollten Sie die Dienst-Lebensdauer sorgfältig berücksichtigen.

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. Wenn die App abgeschlossen ist, sollte die folgende Ausgabe angezeigt werden:

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 der Dienste, die Sie im ServiceCollectionBereich 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 wird verwendet, um eine ServiceDescriptor Instanz hinzuzufügen, 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