Een veelvoorkomend patroon dat kan worden gebruikt om modulariteit in de codebasis van een toepassing te vergroten met behulp van het MVVM-patroon, is het gebruik van een vorm van inversie van controle. Een van de meest voorkomende oplossingen is met name het gebruik van afhankelijkheidsinjectie, dat bestaat uit het maken van een aantal services die worden geïnjecteerd in back-endklassen (d.i. doorgegeven als parameters aan de viewmodelconstructors) - hiermee kan code die deze services gebruikt niet afhankelijk zijn van implementatiedetails van deze services, en het maakt het ook eenvoudig om de concrete implementaties van deze services te wisselen. Dit patroon maakt het ook eenvoudig om platformspecifieke functies beschikbaar te maken voor back-endcode door ze te abstraheren via een service die vervolgens waar nodig wordt geïnjecteerd.

De MVVM Toolkit biedt geen ingebouwde API’s om het gebruik van dit patroon te vergemakkelijken, omdat er al specifieke bibliotheken hiervoor bestaan, zoals het pakket Microsoft.Extensions.DependencyInjection, dat een volwaardige en krachtige set DI-API’s biedt en fungeert als een eenvoudig in te stellen en te gebruiken IServiceProvider. De volgende handleiding verwijst naar deze bibliotheek en biedt een reeks voorbeelden van hoe u deze integreert in toepassingen met behulp van het MVVM-patroon.

Platform-API's:Ioc

Services configureren en oplossen

De eerste stap is het declareren van een IServiceProvider exemplaar en het initialiseren van alle benodigde services, meestal bij het opstarten. Bijvoorbeeld op UWP (maar een vergelijkbare installatie kan ook worden gebruikt voor andere frameworks):

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 wordt de Services-eigenschap tijdens het opstarten geïnitialiseerd en worden alle applicatieservices en viewmodels geregistreerd. Er is ook een nieuwe Current eigenschap die kan worden gebruikt om eenvoudig toegang te krijgen tot de Services eigenschap vanuit andere weergaven in de toepassing. Bijvoorbeeld:

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

// Use the files service here...

Het belangrijkste aspect hier is dat elke service heel goed platformspecifieke API's kan gebruiken, maar omdat deze allemaal zijn geabstraheerd via de interface die onze code gebruikt, hoeven we ons er geen zorgen over te maken wanneer we alleen een exemplaar oplossen en gebruiken om bewerkingen uit te voeren.

Constructorinjectie

Een krachtige functie die beschikbaar is, is 'constructorinjectie', wat betekent dat de DI-serviceprovider automatisch indirecte afhankelijkheden tussen geregistreerde services kan oplossen bij het maken van exemplaren van het aangevraagde type. Houd rekening met de volgende service:

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 hebben we een FileLogger-type dat de IFileLogger-interface implementeert en waarvoor IFilesService- en IConsoleService-instanties vereist zijn. Constructorinjectie betekent dat de DI-serviceprovider automatisch alle benodigde services verzamelt, zoals:

/// <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>();

De DI-serviceprovider controleert automatisch of alle benodigde services zijn geregistreerd, haalt deze vervolgens op en roept daarna de constructor van het geregistreerde IFileLogger concrete type aan om de te retourneren instantie te verkrijgen.

Hoe zit het met viewmodels?

Een serviceprovider heeft 'service' in de naam, maar kan daadwerkelijk worden gebruikt om instanties van elke klasse, inclusief viewmodels, op te lossen. Dezelfde concepten die hierboven worden uitgelegd, zijn nog steeds van toepassing, inclusief constructorinjectie. Stel dat we een ContactsViewModel-type hadden dat via de constructor gebruikmaakt van een IContactsService- en een IPhoneService-instantie. We kunnen een ConfigureServices methode als volgt hebben:

/// <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();
}

En vervolgens zouden we in onze ContactsView de datacontext als volgt toewijzen:

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

Meer documenten

Zie Microsoft.Extensions.DependencyInjection voor meer informatie over .

Examples

  • Bekijk de voorbeeld-app (voor meerdere UI-frameworks) om de MVVM Toolkit in actie te zien.
  • U vindt ook meer voorbeelden in de eenheidstests.