Freigeben über


Abhängigkeitsinjektion

Tipp

Diese Inhalte sind ein Auszug aus dem eBook „Enterprise Application Patterns Using .NET MAUI“, verfügbar unter .NET Docs oder als kostenlos herunterladbare PDF-Datei, die offline gelesen werden kann.

Enterprise Application Patterns Using .NET MAUI (Miniaturansicht eBook-Deckblatt).

In der Regel wird beim Instanziieren eines Objekts ein Klassenkonstruktor aufgerufen, und alle Werte, die das Objekt benötigt, werden als Argumente an den Konstruktor übergeben. Dies ist ein Beispiel für Dependency Injection, die als Konstruktorinjektion bezeichnet wird. Die Abhängigkeiten, die das Objekt benötigt, werden in den Konstruktor eingefügt.

Durch das Angeben von Abhängigkeiten als Schnittstellentypen ermöglicht Dependency Injection eine Entkopplung der konkreten Typen vom Code, der von diesen Typen abhängt. Im Allgemeinen wird ein Container verwendet, der eine Liste von Registrierungen und Zuordnungen zwischen Schnittstellen und abstrakten Typen sowie die konkreten Typen enthält, die diese Typen implementieren oder erweitern.

Es gibt auch andere Arten von Dependency Injection, z. B. Injektion von Eigenschaftensettern und Injektion von Methodenaufrufen, die jedoch seltener vorkommen. Daher konzentriert sich dieses Kapitel ausschließlich auf die Durchführung von Konstruktorinjektion mit einem Dependency Injection-Container.

Einführung in Dependency Injection

Dependency Injection ist eine spezielle Version des IoC-Musters (Inversion of Control), bei dem das invertierte Problem der Prozess zum Abrufen der erforderlichen Abhängigkeit ist. Mit Dependency Injection ist eine andere Klasse für das Einfügen von Abhängigkeiten in ein Objekt zur Laufzeit verantwortlich. Das folgende Codebeispiel zeigt, wie die ProfileViewModel-Klasse strukturiert ist, wenn Dependency Injection verwendet wird:

private readonly ISettingsService _settingsService;
private readonly IAppEnvironmentService _appEnvironmentService;

public ProfileViewModel(
    IAppEnvironmentService appEnvironmentService,
    IDialogService dialogService, 
    INavigationService navigationService, 
    ISettingsService settingsService)
    : base(dialogService, navigationService, settingsService)
{
    _appEnvironmentService = appEnvironmentService;
    _settingsService = settingsService;

    // Omitted for brevity
}

Der ProfileViewModel-Konstruktor empfängt mehrere Schnittstellenobjektinstanzen als Argumente, die von einer anderen Klasse eingefügt werden. Die einzige Abhängigkeit in der ProfileViewModel-Klasse bezieht sich auf die Schnittstellentypen. Daher besitzt die ProfileViewModel-Klasse keine Kenntnis von der Klasse, die für die Instanziierung der Schnittstellenobjekte verantwortlich ist. Die Klasse, die für das Instanziieren der Schnittstellenobjekte und das Einfügen in die ProfileViewModel-Klasse verantwortlich ist, wird als Dependency Injection-Container bezeichnet.

Container mit Abhängigkeitsinjektion verringern die Kopplung zwischen Objekten, indem sie eine Möglichkeit zum Instanziieren von Klasseninstanzen bereitstellen und deren Lebensdauer basierend auf der Konfiguration des Containers verwalten. Während der Objekterstellung fügt der Container alle Abhängigkeiten ein, die das Objekt benötigt. Wenn diese Abhängigkeiten noch nicht erstellt wurden, erstellt der Container zuerst die Abhängigkeiten und löst sie auf.

Die Verwendung eines Dependency Injection-Containers hat mehrere Vorteile:

  • Ein Container beseitigt die Notwendigkeit, dass eine Klasse nach ihren Abhängigkeiten suchen und deren Lebensdauer verwalten muss.
  • Ein Container ermöglicht die Zuordnung implementierter Abhängigkeiten, ohne dass sich dies auf die Klasse auswirkt.
  • Ein Container erleichtert die Testbarkeit, indem Abhängigkeiten simuliert werden können.
  • Ein Container erhöht die Wartbarkeit, indem der App neue Klassen auf einfache Weise hinzugefügt werden können.

Im Kontext einer .NET MAUI-App, die MVVM verwendet, wird ein Dependency Injection-Container in der Regel zum Registrieren und Auflösen von Ansichten, zum Registrieren und Auflösen von Ansichtsmodellen sowie zum Registrieren und Auflösen von Diensten sowie zum Einfügen dieser Elemente in Ansichtsmodelle verwendet.

In .NET sind viele Container für die Abhängigkeitsinjektion verfügbar. Die eShop-Multiplattform-App verwendet Microsoft.Extensions.DependencyInjection, um die Instanziierung von Ansichten, Ansichtsmodellen und Dienstklassen in der App zu verwalten. Microsoft.Extensions.DependencyInjection ermöglicht das Erstellen von lose gekoppelten Apps und stellt alle Features bereit, die häufig in Dependency Injection-Containern zu finden sind, einschließlich Methoden zum Registrieren von Typzuordnungen und Objektinstanzen, zum Auflösen von Objekten, zum Verwalten von Objektlebensdauern und zum Einfügen abhängiger Objekte in Konstruktoren von Objekten, die aufgelöst werden. Weitere Informationen zu Microsoft.Extensions.DependencyInjection finden Sie unter Dependency Injection in .NET.

In .NET MAUI ruft die MauiProgram-Klasse die CreateMauiApp-Methode auf, um ein MauiAppBuilder-Objekt zu erstellen. Das MauiAppBuilder-Objekt verfügt über eine Services-Eigenschaft vom Typ IServiceCollection, die eine Möglichkeit zum Registrieren unserer Komponenten bietet, z. B von Ansichten, Ansichtsmodellen und Diensten für Dependency Injection. Alle Komponenten, die mit der Services-Eigenschaft registriert sind, werden für den Dependency Injection-Container bereitgestellt, wenn die MauiAppBuilder.Build-Methode aufgerufen wird.

Zur Laufzeit muss der Container wissen, welche Implementierung der Dienste angefordert wird, um sie für die angeforderten Objekte zu instanziieren. In der eShop-Multiplattform-App müssen die IAppEnvironmentService-, IDialogService-, INavigationService- und ISettingsService-Schnittstellen aufgelöst werden, bevor ein ProfileViewModel-Objekt instanziiert werden kann. Dies bedeutet, dass der Container die folgenden Aktionen durchführt:

  • Entscheiden, wie ein Objekt instanziiert werden soll, das die Schnittstelle implementiert. Dies wird als Registrierung bezeichnet.
  • Instanziieren des Objekts, das die erforderliche Schnittstelle und das ProfileViewModel-Objekt implementiert. Dies wird als Auflösung bezeichnet.

Schließlich wird die App die Verwendung des ProfileViewModel-Objekts beenden, und es wird für Garbage Collection verfügbar. Zu diesem Zeitpunkt sollte der Garbage Collector alle kurzlebigen Schnittstellenimplementierungen bereinigen, wenn andere Klassen nicht dieselbe Instanz verwenden.

Registrierung

Bevor Abhängigkeiten in ein Objekt eingefügt werden können, müssen die Typen der Abhängigkeiten zuerst beim Container registriert werden. Beim Registrieren eines Typs wird dem Container eine Schnittstelle und ein konkreter Typ übergeben, der die Schnittstelle implementiert.

Es gibt zwei Möglichkeiten, Typen und Objekte im Container über Code zu registrieren:

  • Registrieren Sie einen Typ oder eine Zuordnung beim Container. Dies wird als vorübergehende Registrierung bezeichnet. Bei Bedarf erstellt der Container eine Instanz des angegebenen Typs.
  • Registrieren Sie ein vorhandenes Objekt im Container als Singleton. Bei Bedarf gibt der Container einen Verweis auf das vorhandene Objekt zurück.

Hinweis

Dependency Injection-Container sind nicht immer geeignet. Dependency Injection führt zu zusätzlicher Komplexität und Anforderungen, die für kleine Apps möglicherweise nicht geeignet oder nützlich sind. Wenn eine Klasse keine Abhängigkeiten aufweist oder keine Abhängigkeit für andere Typen ist, ist es möglicherweise nicht sinnvoll, sie im Container zu platzieren. Wenn eine Klasse über einen einzelnen Satz von Abhängigkeiten verfügt, die für den Typ integral sind und sich nie ändern, ist es möglicherweise nicht sinnvoll, sie im Container zu platzieren.

Die Registrierung von Typen, die Dependency Injection erfordern, sollte in einer einzelnen Methode in einer App ausgeführt werden. Diese Methode sollte zu einem frühen Zeitpunkt im Lebenszyklus der App aufgerufen werden, um sicherzustellen, dass sie die Abhängigkeiten zwischen ihren Klassen kennt. Bei der eShop-Multiplattform-App erfolgt dies mit der MauiProgram.CreateMauiApp-Methode. Das folgende Codebeispiel zeigt, wie die eShop-Multiplattform-App CreateMauiApp in der MauiProgram-Klasse deklariert:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
        => MauiApp.CreateBuilder()
            .UseMauiApp<App>()
            // Omitted for brevity            
            .RegisterAppServices()
            .RegisterViewModels()
            .RegisterViews()
            .Build();
}

Die MauiApp.CreateBuilder-Methode erstellt ein MauiAppBuilder-Objekt, mit dem wir unsere Abhängigkeiten registrieren können. Viele Abhängigkeiten in der eShop-Multiplattform-App müssen registriert werden. Daher wurden die Erweiterungsmethoden RegisterAppServices, RegisterViewModels und RegisterViews erstellt, um einen organisierten und verwaltbaren Registrierungsworkflow bereitzustellen. Der folgende Code veranschaulicht die RegisterViewModels-Methode:

public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ViewModels.MainViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.LoginViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.BasketViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.CatalogViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.ProfileViewModel>();

    mauiAppBuilder.Services.AddTransient<ViewModels.CheckoutViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.OrderDetailViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.SettingsViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.CampaignViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.CampaignDetailsViewModel>();

    return mauiAppBuilder;
}

Diese Methode empfängt eine Instanz von MauiAppBuilder, und wir können die Services-Eigenschaft verwenden, um unsere Ansichtsmodelle zu registrieren. Abhängig von den Anforderungen Ihrer Anwendung müssen Sie möglicherweise Dienste mit unterschiedlichen Lebensdauern hinzufügen. In der folgenden Tabelle finden Sie Informationen dazu, wann Sie diese verschiedenen Registrierungszeiträume auswählen sollten:

Methode BESCHREIBUNG
AddSingleton<T> Erstellt eine einzelne Instanz des Objekts, die für die Lebensdauer der Anwendung vorhanden ist.
AddTransient<T> Erstellt eine neue Instanz des Objekts, wenn während der Auflösung angefordert. Vorübergehende Objekte verfügen nicht über eine vordefinierte Lebensdauer, sondern folgen in der Regel der Lebensdauer ihres Hosts.

Hinweis

Die Ansichtsmodelle erben nicht von einer Schnittstelle, sodass sie nur ihren konkreten Typ benötigen, der für die AddSingleton<T>- und AddTransient<T>-Methoden bereitgestellt wird.

CatalogViewModel wird in der Nähe des Anwendungsstamms verwendet und sollte immer verfügbar sein, daher ist die Registrierung mit AddSingleton<T> vorteilhaft. Zu anderen Ansichtsmodellen, z. B. CheckoutViewModel und OrderDetailViewModel, wird situationsabhängig navigiert, oder sie werden später in der Anwendung verwendet. Angenommen, Sie wissen, dass Sie über eine Komponente verfügen, die möglicherweise nicht immer verwendet wird. In diesem Fall ist sie möglicherweise ein besserer Kandidat für AddTransient<T>-Registrierung, wenn sie arbeitsspeicher- oder rechenintensiv ist oder Just-In-Time-Daten erfordert.

Eine weitere gängige Möglichkeit zum Hinzufügen von Diensten ist die Verwendung der AddSingleton<TService, TImplementation>- und AddTransient<TService, TImplementation>-Methoden. Diese Methoden nehmen zwei Eingabetypen an: die Schnittstellendefinition und die konkrete Implementierung. Diese Art der Registrierung eignet sich am besten für Fälle, in denen Sie Dienste basierend auf Schnittstellen implementieren. Im folgenden Codebeispiel registrieren wir die ISettingsService-Schnittstelle mithilfe der SettingsService-Implementierung:

public static MauiAppBuilder RegisterAppServices(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ISettingsService, SettingsService>();
    // Omitted for brevity...
}

Nachdem alle Dienste registriert wurden, sollte die MauiAppBuilder.Build-Methode aufgerufen werden, um unsere MauiApp zu erstellen und den Dependency Injection-Container mit allen registrierten Diensten aufzufüllen.

Wichtig

Nachdem die Build-Methode aufgerufen wurde, ist der Abhängigkeitsinjektionscontainer unveränderlich und kann nicht mehr aktualisiert oder geändert werden. Stellen Sie sicher, dass alle in Ihrer Anwendung benötigten Dienste registriert wurden, bevor Sie Build aufrufen.

Lösung

Nachdem ein Typ registriert wurde, kann er aufgelöst oder als Abhängigkeit eingefügt werden. Wenn ein Typ aufgelöst wird und der Container eine neue Instanz erstellen muss, fügt er alle Abhängigkeiten in die Instanz ein.

Wenn ein Typ aufgelöst wird, geschieht im Allgemeinen eines von drei Dingen:

  1. Wenn der Typ nicht registriert wurde, löst der Container eine Ausnahme aus.
  2. Wenn der Typ als Singleton registriert wurde, gibt der Container die Singleton-Instanz zurück. Wenn der Typ zum ersten Mal aufgerufen wird, erstellt der Container ihn bei Bedarf und verwaltet einen Verweis darauf.
  3. Wenn der Typ als vorübergehend registriert wurde, gibt der Container eine neue Instanz zurück und verwaltet keinen Verweis darauf.

.NET MAUI bietet eine Reihe von Möglichkeiten, registrierte Komponenten basierend auf Ihren Anforderungen aufzulösen. Die direkteste Möglichkeit, Zugriff auf den Dependency Injection-Container zu erhalten, besteht darin, ein Element mithilfe von Handler.MauiContext.Services zu verwenden. Ein Beispiel hierfür wird unten gezeigt:

var settingsService = this.Handler.MauiContext.Services.GetServices<ISettingsService>();

Dies kann hilfreich sein, wenn Sie einen Dienst aus einem Element oder außerhalb des Konstruktors Ihres Element auflösen müssen.

Achtung

Es besteht die Möglichkeit, dass die Handler-Eigenschaft Ihres Element NULL ist. Beachten Sie daher, dass Sie diesen Umstand möglicherweise berücksichtigen müssen. Weitere Informationen finden Sie unter Handlerlebenszyklus im Microsoft-Dokumentationscenter.

Wenn Sie das Shell-Steuerelement für .NET MAUI verwenden, ruft es implizit den Dependency Injection-Container auf, um unsere Objekte während der Navigation zu erstellen. Beim Einrichten des Shell-Steuerelements bindet die Routing.RegisterRoute-Methode einen Routenpfad an eine View, wie im folgenden Beispiel gezeigt:

Routing.RegisterRoute("Filter", typeof(FiltersView));

Während der Shell-Navigation sucht es nach Registrierungen von FiltersView, und wenn eine solche gefunden wird, erstellt es diese Ansicht und fügt alle Abhängigkeiten in den Konstruktor ein. Wie im folgenden Codebeispiel gezeigt, wird das CatalogViewModel in die FiltersView eingefügt:

namespace eShop.Views;

public partial class FiltersView : ContentPage
{
    public FiltersView(CatalogViewModel viewModel)
    {
        BindingContext = viewModel;

        InitializeComponent();
    }
}

Tipp

Der Dependency Injection-Container eignet sich hervorragend zum Erstellen von Ansichtsmodellinstanzen. Wenn ein Ansichtsmodell Abhängigkeiten aufweist, übernimmt es die Erstellung und das Hinzufügen aller erforderlichen Dienste. Stellen Sie nur sicher, dass Sie Ihre Ansichtsmodelle und alle eventuellen Abhängigkeiten mit der CreateMauiApp-Methode in der MauiProgram-Klasse registrieren.

Zusammenfassung

Dependency Injection ermöglicht die Entkopplung konkreter Typen vom Code, der von diesen Typen abhängt. Im Allgemeinen wird ein Container verwendet, der eine Liste von Registrierungen und Zuordnungen zwischen Schnittstellen und abstrakten Typen sowie die konkreten Typen enthält, die diese Typen implementieren oder erweitern.

Microsoft.Extensions.DependencyInjection ermöglicht das Erstellen von lose gekoppelten Apps und stellt alle Features bereit, die häufig in Dependency Injection-Containern zu finden sind, einschließlich Methoden zum Registrieren von Typzuordnungen und Objektinstanzen, zum Auflösen von Objekten, zum Verwalten von Objektlebensdauern und zum Einfügen abhängiger Objekte in Konstruktoren von Objekten, die aufgelöst werden.