Ett vanligt mönster som kan användas för att öka modulariteten i kodbasen för ett program med MVVM-mönstret är att använda någon form av inversion av kontrollen. En av de vanligaste lösningarna i synnerhet är att använda beroendeinmatning, som består i att skapa ett antal tjänster som matas in i serverdelsklasser (dvs. skickas som parametrar till viewmodel-konstruktorerna) – detta gör att kod som använder dessa tjänster inte kan förlita sig på implementeringsinformation för dessa tjänster, och det gör det också enkelt att byta de konkreta implementeringarna av dessa tjänster. Det här mönstret gör det också enkelt att göra plattformsspecifika funktioner tillgängliga för serverdelskod genom att abstrahera dem via en tjänst som sedan matas in där det behövs.

MVVM Toolkit tillhandahåller inte inbyggda API:er för att underlätta användningen av det här mönstret, eftersom det redan finns dedikerade bibliotek specifikt för detta, till exempel paketet Microsoft.Extensions.DependencyInjection, som tillhandahåller en fullständigt aktuell och kraftfull DI-uppsättning API:er och fungerar som en lätt att konfigurera och använda IServiceProvider. Följande guide refererar till det här biblioteket och innehåller en serie exempel på hur du integrerar det i program med MVVM-mönstret.

Plattforms-API:er:Ioc

Konfigurera och lösa tjänster

Det första steget är att deklarera en IServiceProvider instans och initiera alla nödvändiga tjänster, vanligtvis vid start. Till exempel på UWP (men en liknande konfiguration kan även användas på andra ramverk):

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

Services Här initieras egenskapen vid start och alla programtjänster och viewmodels registreras. Det finns också en ny Current egenskap som kan användas för att enkelt komma åt Services egenskapen från andra vyer i programmet. Till exempel:

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

// Use the files service here...

Den viktigaste aspekten här är att varje tjänst mycket väl kan använda plattformsspecifika API:er, men eftersom alla är abstraherade via gränssnittet som vår kod använder behöver vi inte bekymra oss om dem när vi bara löser en instans och använder den för att utföra åtgärder.

Konstruktorinjektion

En kraftfull funktion som är tillgänglig är "konstruktorinmatning", vilket innebär att DI-tjänstleverantören automatiskt kan lösa indirekta beroenden mellan registrerade tjänster när instanser av den typ som begärs skapas. Överväg följande tjänst:

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...
}

Här har vi en FileLogger typ som implementerar IFileLogger gränssnittet och kräver IFilesService och IConsoleService instanser. Konstruktorinmatning innebär att DI-tjänstleverantören automatiskt samlar in alla nödvändiga tjänster, så här:

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

DI-tjänstleverantören kommer automatiskt att kontrollera om alla nödvändiga tjänster är registrerade, sedan hämta dem och anropa konstruktorn för den registrerade konkreta typen IFileLogger för att hämta den instans som ska returneras.

Hur är det med viewmodels?

En tjänstleverantör har "tjänst" i sitt namn, men den kan faktiskt användas för att lösa instanser av alla klasser, inklusive viewmodels! De begrepp som förklaras ovan gäller fortfarande, inklusive konstruktorinjektion. Anta att vi hade en ContactsViewModel-typ som använder en IContactsService-instans och en IPhoneService-instans via sin konstruktor. Vi kan ha en ConfigureServices metod som den här:

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

Och sedan tilldelar vi datakontexten enligt följande i vår ContactsView:

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

Fler dokument

Mer information om Microsoft.Extensions.DependencyInjection finns i here.

Exempel

  • Kolla in exempelappen (för flera gränssnittsramverk) för att se MVVM Toolkit i praktiken.
  • Du kan också hitta fler exempel i enhetstesterna.