Ioc (Inversion du contrôle)

Un modèle courant pour augmenter la modularité dans la base de code d’une application à l’aide du modèle MVVM consiste à utiliser une forme d’inversion de contrôle. L’une des solutions les plus courantes consiste en particulier à utiliser l’injection de dépendances, qui crée un certain nombre de services injectés dans des classes back-end (c’est-à-dire transmis en tant que paramètres aux constructeurs viewmodel) : cela permet au code d’utiliser ces services pour ne pas compter sur les détails de l’implémentation de ces services, et permet également de permuter facilement les implémentations concrètes de ces services. Ce modèle facilite également la mise à disposition de fonctionnalités spécifiques à la plateforme pour le code principal, en les extrayant par le biais d’un service qui est ensuite injecté si nécessaire.

Le kit de ressources MVVM ne fournit pas d’API intégrées pour faciliter l’utilisation de ce modèle, car il existe déjà des bibliothèques dédiées spécifiquement pour cela, telles que le package Microsoft.Extensions.DependencyInjection, qui fournit un ensemble complet et puissant d’API, et agit comme un ensemble facile à configurer et à utiliser IServiceProvider. Le guide suivant fait référence à cette bibliothèque et fournit une série d’exemples d’intégration dans des applications à l’aide du modèle MVVM.

API de plateforme :Ioc

Configurer et résoudre les services

La première étape consiste à déclarer une instance IServiceProvider et à initialiser tous les services nécessaires, généralement au démarrage. Par exemple, sur UWP (mais une configuration similaire peut également être utilisée sur d’autres 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();
    }
}

Ici, la propriété Services est initialisée au démarrage, et tous les services d’application et les viewmodels sont inscrits. Il existe également une nouvelle propriété Current qui peut être utilisée pour accéder facilement à la propriété Services à partir d’autres vues de l’application. Exemple :

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

// Use the files service here...

L’aspect clé ici est que chaque service peut très bien utiliser des API spécifiques à la plateforme, mais étant donné que ceux-ci sont tous abstraits via l’interface que notre code utilise, nous n’avons pas besoin de s’en préoccuper chaque fois que nous résolvons une simple instance et que nous l’utilisons pour effectuer des opérations.

Injection de constructeurs

L’« injection de constructeurs » est une fonctionnalité puissante. Elle indique que le fournisseur de services d’adresse d’entreprise est en mesure de résoudre automatiquement les dépendances indirectes entre les services inscrits lors de la création d’instances du type demandé. Considérez le service suivant :

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

Ici, nous avons un type FileLogger implémentant l’interface IFileLogger et nécessitant des instances IFilesService et IConsoleService. L’injection de constructeurs indique que le fournisseur de services d’injection de dépendances collecte automatiquement tous les services nécessaires de la manière suivante :

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

Le fournisseur de services d’adresse de domaine vérifie automatiquement si tous les services nécessaires sont inscrits, puis les récupère et appelle le constructeur pour le type concret IFileLogger inscrit, afin que l’instance soit retournée.

Qu’en est-il des viewmodels ?

Un fournisseur de services présente « service » dans son nom, mais il peut en fait être utilisé pour résoudre les instances de n’importe quelle classe, y compris viewmodels ! Les mêmes concepts expliqués ci-dessus s’appliquent toujours, y compris l’injection de constructeurs. Imaginez que nous disposons d’un type ContactsViewModel, à l’aide d’un IContactsService et d’une instance IPhoneService par le biais de son constructeur. Nous pourrions avoir une méthode ConfigureServices comme ceci :

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

Ensuite, dans notre ContactsView, nous allons affecter le contexte de données de cette manière :

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

Autres documents

Pour plus d’informations sur Microsoft.Extensions.DependencyInjection, consultez ceci.

Exemples

  • Consultez l’exemple d’application (pour plusieurs infrastructures d’interface utilisateur) pour voir le kit d’outils MVVM à l’œuvre.
  • Vous trouverez également d’autres exemples dans les tests unitaires.