Um padrão comum que pode ser usado para aumentar a modularidade na base de código de uma aplicação usando o padrão MVVM é usar algum tipo de inversão de controlo. Uma das soluções mais comuns, em particular, é usar injeção de dependências, que consiste em criar vários serviços que são injetados em classes backend (ou seja, passados como parâmetros para os construtores do viewmodel) – isto permite que o código que utiliza estes serviços não dependa dos detalhes da implementação desses serviços, e também facilita a troca das implementações concretas desses serviços. Este padrão também facilita disponibilizar funcionalidades específicas da plataforma ao código backend, abstraindo-as através de um serviço que é depois injetado onde necessário.

O MVVM Toolkit não fornece APIs integradas para facilitar a utilização deste padrão, uma vez que já existem bibliotecas dedicadas especificamente a esse fim, como o pacote Microsoft.Extensions.DependencyInjection, que fornece um conjunto completo e robusto de APIs de DI e funciona como um IServiceProvider fácil de configurar e utilizar. O guia seguinte referir-se-á a esta biblioteca e fornecerá uma série de exemplos de como a integrar em aplicações usando o padrão MVVM.

APIs de Plataforma:Ioc

Configurar e resolver serviços

O primeiro passo é declarar uma IServiceProvider instância e inicializar todos os serviços necessários, normalmente no arranque. Por exemplo, em UWP (mas uma configuração semelhante pode ser usada noutros frameworks também):

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

Aqui, a Services propriedade é inicializada no arranque, e todos os serviços de aplicação e modelos de visualização são registados. Existe também uma nova Current propriedade que pode ser usada para aceder facilmente à Services propriedade a partir de outras perspetivas na aplicação. Por exemplo:

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

// Use the files service here...

O aspeto chave aqui é que cada serviço pode muito bem estar a usar APIs específicas da plataforma, mas como todas essas são abstraídas através da interface que o nosso código utiliza, não precisamos de nos preocupar com elas quando estamos apenas a resolver uma instância e a usar para realizar operações.

Injeção por construtor

Uma funcionalidade poderosa que está disponível é a "injeção via construtor", o que significa que o fornecedor de serviços de DI consegue resolver automaticamente dependências indiretas entre serviços registados ao criar instâncias do tipo solicitado. Considere o seguinte serviço:

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

Aqui temos um tipo FileLogger que implementa a interface IFileLogger e requer instâncias de IFilesService e IConsoleService. Injeção de construtores significa que o fornecedor de serviços DI irá automaticamente reunir todos os serviços necessários, da seguinte forma:

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

O fornecedor de serviços DI verifica automaticamente se todos os serviços necessários estão registados, depois recupera-os e invoca o construtor para o tipo de betão registado IFileLogger , para que a instância seja devolvida.

E quanto aos viewmodels?

Um fornecedor de serviços tem "service" no nome, mas pode ser usado para resolver instâncias de qualquer classe, incluindo viewmodels! Os mesmos conceitos explicados acima continuam a aplicar-se, incluindo a injeção através do construtor. Imagine que temos um tipo ContactsViewModel, que utiliza um IContactsService e uma instância de IPhoneService no seu construtor. Poderíamos ter um ConfigureServices método assim:

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

E depois, no nosso ContactsView, atribuiríamos o contexto dos dados da seguinte forma:

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

Mais documentação

Para mais informações sobre Microsoft.Extensions.DependencyInjection, veja aqui.

Exemplos

  • Dá uma vista de olhos à aplicação de exemplo (para múltiplos frameworks de interface) para veres o MVVM Toolkit em ação.
  • Também podes encontrar mais exemplos nos testes unitários.