Freigeben über


Ioc (Inversion der Kontrolle)

Ein gängiges Muster, das verwendet werden kann, um die Modularität in der Codebasis einer Anwendung mithilfe des MVVM-Musters zu erhöhen, besteht darin, eine Form von Inversion der Kontrolle zu verwenden. Eine der am häufigsten verwendeten Lösungen ist insbesondere die Verwendung von Abhängigkeitseinfügungen, die darin besteht, eine Reihe von Diensten zu erstellen, die in Back-End-Klassen (d. h. als Parameter an die Viewmodel-Konstruktoren übergeben werden) - dies ermöglicht Code, der diese Dienste nicht auf Implementierungsdetails dieser Dienste basiert, und es erleichtert auch das Austauschen der konkreten Implementierungen dieser Dienste. Dieses Muster erleichtert auch die Bereitstellung plattformspezifischer Features für Back-End-Code, indem sie durch einen Dienst abstrahiert werden, der bei Bedarf eingefügt wird.

Das MVVM-Toolkit bietet keine integrierten APIs, um die Verwendung dieses Musters zu erleichtern, da es bereits dedizierte Bibliotheken speziell für dieses Modell gibt, z. B. das Microsoft.Extensions.DependencyInjection-Paket, das einen vollständig bereitgestellten und leistungsstarken DI-Satz von APIs bereitstellt, und fungiert als einfach einzurichten und IServiceProvider zu verwenden. Die folgende Anleitung enthält eine Reihe von Beispielen für die Integration in Anwendungen mithilfe des MVVM-Musters.

Plattform-APIs: Ioc

Konfigurieren und Auflösen von Diensten

Der erste Schritt besteht darin, eine IServiceProvider-Instanz zu deklarieren und alle erforderlichen Dienste zu initialisieren, normalerweise beim Start. Beispielsweise kann unter UWP ein ähnliches Setup auch für andere Frameworks verwendet werden:

public sealed partial class App : Application
{
    public App()
    {
        Services = ConfigureServices();

        this.InitializeComponent();
    }

    /// <summary>
    /// Gets the current <see cref="App"/> instance in use
    /// </summary>
    public new static App Current => (App)Application.Current;

    /// <summary>
    /// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
    /// </summary>
    public IServiceProvider Services { get; }

    /// <summary>
    /// Configures the services for the application.
    /// </summary>
    private static IServiceProvider ConfigureServices()
    {
        var services = new ServiceCollection();

        services.AddSingleton<IFilesService, FilesService>();
        services.AddSingleton<ISettingsService, SettingsService>();
        services.AddSingleton<IClipboardService, ClipboardService>();
        services.AddSingleton<IShareService, ShareService>();
        services.AddSingleton<IEmailService, EmailService>();

        return services.BuildServiceProvider();
    }
}

Hier wird die Services-Eigenschaft beim Start initialisiert, und alle Anwendungsdienste und Viewmodels werden registriert. Es gibt auch eine neue Current-Eigenschaft, die verwendet werden kann, um auf einfache Weise über andere Ansichten in der Anwendung auf die Services-Eigenschaft zuzugreifen. Beispiel:

IFilesService filesService = App.Current.Services.GetService<IFilesService>();

// Use the files service here...

Der wichtigste Aspekt hier ist, dass jeder Dienst möglicherweise sehr gut plattformspezifische APIs verwendet, aber da diese alle über die Schnittstelle abstrahiert sind, die unser Code verwendet, müssen wir uns nicht um sie kümmern, wenn wir nur eine Instanz auflösen und sie zum Ausführen von Vorgängen verwenden.

Constructor Injection

Ein leistungsfähiges Feature, das verfügbar ist, ist „Konstruktoreinfügung“, was bedeutet, dass der DI-Dienstanbieter indirekte Abhängigkeiten zwischen registrierten Diensten automatisch auflösen kann, wenn Instanzen des angeforderten Typs erstellt werden. Betrachten Sie den folgenden Dienst:

public class FileLogger : IFileLogger
{
    private readonly IFilesService FileService;
    private readonly IConsoleService ConsoleService;

    public FileLogger(
        IFilesService fileService,
        IConsoleService consoleService)
    {
        FileService = fileService;
        ConsoleService = consoleService;
    }

    // Methods for the IFileLogger interface here...
}

Hier haben wir einen FileLogger-Typ, der die IFileLogger-Schnittstelle implementiert und die Instanzen IFilesService und IConsoleService benötigt. Die Konstruktorinjektion bedeutet, dass der DI-Dienstanbieter automatisch alle erforderlichen Dienste sammelt, z. B.:

/// <summary>
/// Configures the services for the application.
/// </summary>
private static IServiceProvider ConfigureServices()
{
    var services = new ServiceCollection();

    services.AddSingleton<IFilesService, FilesService>();
    services.AddSingleton<IConsoleService, ConsoleService>();
    services.AddSingleton<IFileLogger, FileLogger>();

    return services.BuildServiceProvider();
}

// Retrieve a logger service with constructor injection
IFileLogger fileLogger = App.Current.Services.GetService<IFileLogger>();

Der DI-Dienstanbieter überprüft automatisch, ob alle erforderlichen Dienste registriert sind, und ruft sie dann ab und ruft den Konstruktor für den registrierten IFileLogger konkreten Typ auf, um die Instanz zurückzugeben.

Was ist mit Viewmodels?

Ein Dienstanbieter hat „Dienst“ in seinem Namen, kann aber tatsächlich verwendet werden, um Instanzen jeder Klasse aufzulösen, einschließlich Viewmodels! Die oben erläuterten Konzepte gelten weiterhin, einschließlich der Konstruktoreinfügung. Stellen Sie sich vor, wir hatten einen ContactsViewModel-Typ und mit IContactsService und eine IPhoneService-Instanz über den Konstruktor. Wir könnten eine ConfigureServices-Methode wie folgt haben:

/// <summary>
/// Configures the services for the application.
/// </summary>
private static IServiceProvider ConfigureServices()
{
    var services = new ServiceCollection();

    // Services
    services.AddSingleton<IContactsService, ContactsService>();
    services.AddSingleton<IPhoneService, PhoneService>();

    // Viewmodels
    services.AddTransient<ContactsViewModel>();

    return services.BuildServiceProvider();
}

Und dann würden wir in unserem ContactsView folgenden den Datenkontext zuweisen:

public ContactsView()
{
    this.InitializeComponent();
    this.DataContext = App.Current.Services.GetService<ContactsViewModel>();
}

Weitere Dokumente

Weitere Informationen über Microsoft.Extensions.DependencyInjection finden Sie hier.

Beispiele

  • Sehen Sie sich die Beispiel-App (für mehrere Benutzeroberflächen-Frameworks) an, um das MVVM-Toolkit in Aktion zu sehen.
  • Weitere Beispiele finden Sie auch in den Komponententests.