Partager via


Injection de dépendances

Conseil

Ce contenu est un extrait du livre électronique Modèles d’application d’entreprise avec .NET MAUI, disponible dans la .documentation .NET ou en tant que PDF téléchargeable gratuitement qui peut être lu hors connexion.

Miniature de la couverture du livre électronique Enterprise Application Patterns Using .NET MAUI.

En règle générale, un constructeur de classe est appelé au moment de l’instanciation d’un objet, et toutes les valeurs dont l’objet a besoin sont passées en tant qu’arguments au constructeur. Il s’agit d’un exemple d’injection de dépendances appelée injection de constructeurs. Les dépendances dont l’objet a besoin sont injectées dans le constructeur.

En spécifiant des dépendances en tant que types d’interface, l’injection de dépendances permet de découpler les types concrets du code qui dépend de ces types. Elle utilise généralement un conteneur qui détient une liste d’inscriptions et de mappages entre les interfaces et les types abstraits ainsi que les types concrets qui implémentent ou étendent ces types.

Il existe également d’autres types d’injection de dépendances, par exemple l’injection de setter de propriété et l’injection d’appels de méthode, mais ils sont moins répandus. Ainsi, ce chapitre se concentre uniquement sur l’injection de constructeurs avec un conteneur d’injection de dépendances.

Présentation de l’injection de dépendances

L’injection de dépendances est une version spécialisée du modèle IoC (inversion de contrôle), où l’inversion correspond au processus d’obtention de la dépendance nécessaire. Avec l’injection de dépendances, une autre classe est chargée d’injecter les dépendances dans un objet au moment de l’exécution. L’exemple de code suivant montre comment la classe ProfileViewModel est structurée durant l’utilisation de l’injection de dépendances :

private readonly ISettingsService _settingsService;
private readonly IAppEnvironmentService _appEnvironmentService;

public ProfileViewModel(
    IAppEnvironmentService appEnvironmentService,
    IDialogService dialogService, 
    INavigationService navigationService, 
    ISettingsService settingsService)
    : base(dialogService, navigationService, settingsService)
{
    _appEnvironmentService = appEnvironmentService;
    _settingsService = settingsService;

    // Omitted for brevity
}

Le constructeur ProfileViewModel reçoit plusieurs instances d’objet d’interface en tant qu’arguments injectés par une autre classe. La seule dépendance dans la classe ProfileViewModel concerne les types d’interface. La classe ProfileViewModel ne sait donc pas quelle est la classe responsable de l’instanciation des objets d’interface. La classe responsable de l’instanciation des objets d’interface et de leur insertion dans la classe ProfileViewModel est appelée conteneur d’injection de dépendances.

Les conteneurs d’injection de dépendances réduisent le couplage entre les objets en offrant une fonctionnalité permettant d’instancier les instances de classe, et de gérer leur durée de vie en fonction de la configuration du conteneur. Durant la création de l’objet, le conteneur injecte toutes les dépendances nécessaires à l’objet. Si ces dépendances n’ont pas encore été créées, le conteneur crée et résout d’abord leurs dépendances.

L’utilisation d’un conteneur d’injection de dépendances présente plusieurs avantages :

  • Avec un conteneur, une classe n’a pas besoin de localiser ses dépendances et de gérer ses durées de vie.
  • Un conteneur permet le mappage des dépendances implémentées sans affecter la classe.
  • Un conteneur facilite les tests en permettant de simuler les dépendances.
  • Un conteneur augmente la maintenabilité en facilitant l’ajout de nouvelles classes à l’application.

Dans le contexte d’une application .NET MAUI qui utilise MVVM, un conteneur d’injection de dépendances est généralement utilisé pour l’inscription et la résolution des vues, l’inscription et la résolution des modèles de vue ainsi que l’inscription des services et leur injection dans les modèles de vue.

Il existe de nombreux conteneurs d’injection de dépendances disponibles dans .NET. L’application multiplateforme eShop utilise Microsoft.Extensions.DependencyInjection pour gérer l’instanciation des vues, des modèles de vue et des classes de service dans l’application. Microsoft.Extensions.DependencyInjection facilite la création d’applications faiblement couplées et fournit toutes les fonctionnalités couramment trouvées dans les conteneurs d’injection de dépendances, notamment des méthodes pour inscrire les mappages de types et les instances d’objets, résoudre les objets, gérer les durées de vie des objets et injecter des objets dépendants dans les constructeurs des objets qu’il résout. Pour plus d’informations sur Microsoft.Extensions.DependencyInjection, consultez Injection de dépendances dans .NET.

Dans .NET MAUI, la classe MauiProgram appelle la méthode CreateMauiApp pour créer un objet MauiAppBuilder. L’objet MauiAppBuilder a une propriété Services de type IServiceCollection, qui fournit un emplacement où inscrire nos composants, par exemple les vues, les modèles de vue et les services pour l’injection de dépendances. Tous les composants inscrits auprès de la propriété Services sont fournis au conteneur d’injection de dépendances quand la méthode MauiAppBuilder.Build est appelée.

Au moment de l’exécution, le conteneur doit savoir quelle implémentation des services est demandée afin de les instancier pour les objets demandés. Dans l’application multiplateforme eShop, les interfaces IAppEnvironmentService, IDialogService, INavigationService et ISettingsService doivent être résolues pour permettre l’instanciation d’un objet ProfileViewModel. Cela implique que le conteneur effectue les actions suivantes :

  • Choix du mode d’instanciation d’un objet qui implémente l’interface. Cela s’appelle l’inscription.
  • Instanciation de l’objet qui implémente l’interface nécessaire et de l’objet ProfileViewModel. Cela s’appelle la résolution.

Une fois que l’application a fini d’utiliser l’objet ProfileViewModel, il est disponible pour le nettoyage de la mémoire. À ce stade, le récupérateur de mémoire doit supprimer toutes les implémentations d’interface de courte durée, si les autres classes ne partagent pas la même instance.

Inscription

Pour permettre l’injection de dépendances dans un objet, les types des dépendances doivent être inscrits auprès du conteneur. L’inscription d’un type implique le passage au conteneur d’une interface et d’un type concret qui implémente l’interface.

Il existe deux façons d’inscrire les types et les objets dans le conteneur via du code :

  • Inscrire un type ou un mappage auprès du conteneur. Cela s’appelle l’inscription temporaire. Le cas échéant, le conteneur crée une instance du type spécifié.
  • Inscrire un objet existant dans le conteneur en tant que singleton. Le cas échéant, le conteneur retourne une référence à l’objet existant.

Remarque

Les conteneurs d’injection de dépendances ne conviennent pas toujours. L’injection de dépendances introduit une complexité et des besoins supplémentaires qui ne sont pas toujours appropriés ou utiles pour les petites applications. Si une classe n’a aucune dépendance, ou si elle ne représente pas une dépendance pour d’autres types, il n’est peut-être pas judicieux de la placer dans le conteneur. De plus, si une classe a un seul ensemble de dépendances qui font partie intégrante du type et qui ne changent jamais, il n’est peut-être pas judicieux de la placer dans le conteneur.

L’inscription des types nécessitant une injection de dépendances doit être effectuée au sein d’une seule méthode dans une application. Vous devez appeler cette méthode au début du cycle de vie de l’application pour vérifier qu’elle connaît les dépendances entre ses classes. L’application multiplateforme eShop effectue cette opération à l’aide de la méthode MauiProgram.CreateMauiApp. L’exemple de code suivant montre comment l’application multiplateforme eShop déclare le CreateMauiApp dans la classe MauiProgram :

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
        => MauiApp.CreateBuilder()
            .UseMauiApp<App>()
            // Omitted for brevity            
            .RegisterAppServices()
            .RegisterViewModels()
            .RegisterViews()
            .Build();
}

La méthode MauiApp.CreateBuilder crée un objet MauiAppBuilder que nous pouvons utiliser pour inscrire nos dépendances. Dans la mesure où de nombreuses dépendances dans l’application multiplateforme eShop doivent être inscrites, les méthodes d’extension RegisterAppServices, RegisterViewModels et RegisterViews ont été créées pour fournir un workflow d’inscription organisé et facile à gérer. Le code suivant montre la méthode RegisterViewModels :

public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ViewModels.MainViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.LoginViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.BasketViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.CatalogViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.ProfileViewModel>();

    mauiAppBuilder.Services.AddTransient<ViewModels.CheckoutViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.OrderDetailViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.SettingsViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.CampaignViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.CampaignDetailsViewModel>();

    return mauiAppBuilder;
}

Cette méthode reçoit une instance de MauiAppBuilder, et nous pouvons utiliser la propriété Services pour inscrire nos modèles de vue. En fonction des besoins de votre application, vous devrez peut-être ajouter des services ayant des durées de vie distinctes. Le tableau suivant fournit des informations sur le moment où vous pouvez choisir ces différentes durées de vie d’inscription :

Méthode Description
AddSingleton<T> Crée une seule instance de l’objet qui est conservée pendant toute la durée de vie de l’application.
AddTransient<T> Crée une instance de l’objet quand cela est demandé au cours de la résolution. Les objets temporaires n’ont pas de durée de vie prédéfinie, mais ils suivent généralement la durée de vie de leur hôte.

Remarque

Dans la mesure où les modèles de vue n’héritent pas d’une interface, ils ont uniquement besoin de leur type concret pour les méthodes AddSingleton<T> et AddTransient<T>.

Le CatalogViewModel est utilisé à proximité de la racine de l’application et doit toujours être disponible. Il est donc avantageux de l’inscrire auprès de AddSingleton<T>. D’autres modèles de vue, par exemple CheckoutViewModel et OrderDetailViewModel, font l’objet d’un accès en fonction de la situation ou sont utilisés plus tard dans l’application. Supposons que vous sachiez que vous disposez d’un composant qui n’est pas toujours utilisé. Dans ce cas, s’il s’agit d’un processus gourmand en mémoire ou en calcul, ou s’il nécessite des données juste-à-temps, il représente peut-être un meilleur candidat pour l’inscription de AddTransient<T>.

Une autre façon courante d’ajouter des services consiste à utiliser les méthodes AddSingleton<TService, TImplementation> et AddTransient<TService, TImplementation>. Ces méthodes acceptent deux types d’entrée : la définition d’interface et l’implémentation concrète. Ce type d’inscription convient mieux dans les cas où vous implémentez des services basés sur des interfaces. Dans l’exemple de code ci-dessous, nous inscrivons notre interface ISettingsService à l’aide de l’implémentation de SettingsService :

public static MauiAppBuilder RegisterAppServices(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ISettingsService, SettingsService>();
    // Omitted for brevity...
}

Une fois que tous les services ont été inscrits, nous devons appeler la méthode MauiAppBuilder.Build pour créer notre MauiApp et remplir le conteneur d’injection de dépendances avec tous les services inscrits.

Important

Une fois la méthode Build appelée, le conteneur d’injection de dépendances est immuable. Il ne peut plus être mis à jour ou modifié. Vérifiez que tous les services dont vous avez besoin dans votre application ont été inscrits avant d’appeler Build.

Résolution

Une fois qu’un type est inscrit, il peut être résolu ou injecté en tant que dépendance. Quand un type est résolu et que le conteneur doit créer une instance, il injecte les dépendances éventuelles dans l’instance.

En règle générale, quand un type est résolu, l’une des trois situations suivantes se produit :

  1. Si le type n’a pas été inscrit, le conteneur lève une exception.
  2. Si le type a été inscrit en tant que singleton, le conteneur retourne l’instance de singleton. Si le type est appelé pour la première fois, le conteneur le crée le cas échéant, et gère une référence à celui-ci.
  3. Si le type a été inscrit en tant que type temporaire, le conteneur retourne une nouvelle instance et ne conserve pas de référence à celle-ci.

.NET MAUI offre plusieurs façons de résoudre les composants inscrits en fonction des besoins. Le moyen le plus direct d’accéder au conteneur d’injection de dépendances consiste à utiliser Element à partir d’un Handler.MauiContext.Services. En voici un exemple :

var settingsService = this.Handler.MauiContext.Services.GetServices<ISettingsService>();

Cela peut être utile si vous devez résoudre un service au sein d’un Element ou de l’extérieur du constructeur de votre Element.

Attention

Il est possible que la propriété Handler de votre Element ait une valeur nulle. Vous serez donc peut-être amené à gérer ces situations. Pour plus d’informations, consultez le cycle de vie du gestionnaire dans le Centre de documentation Microsoft.

Si vous utilisez le contrôle Shell pour .NET MAUI, il appelle implicitement le conteneur d’injection de dépendances afin de créer les objets durant la navigation. Au moment de la configuration du contrôle Shell, la méthode Routing.RegisterRoute lie un chemin de routage à un View, comme le montre l’exemple ci-dessous :

Routing.RegisterRoute("Filter", typeof(FiltersView));

Durant la navigation du contrôle Shell, elle recherche les inscriptions de FiltersView. Si elle en trouve, elle crée la vue correspondante, puis injecte les dépendances éventuelles dans le constructeur. Comme le montre l’exemple de code ci-dessous, le CatalogViewModel est injecté dans le FiltersView :

namespace eShop.Views;

public partial class FiltersView : ContentPage
{
    public FiltersView(CatalogViewModel viewModel)
    {
        BindingContext = viewModel;

        InitializeComponent();
    }
}

Conseil

Le conteneur d’injection de dépendances est idéal pour créer des instances de modèle de vue. Si un modèle de vue a des dépendances, il gère la création et l’injection de tous les services nécessaires. Veillez simplement à inscrire vos modèles de vue et toutes les dépendances qu’ils peuvent avoir avec la méthode CreateMauiApp dans la classe MauiProgram.

Résumé

L’injection de dépendances permet le découplage des types concrets du code qui dépend de ces types. Elle utilise généralement un conteneur qui détient une liste d’inscriptions et de mappages entre les interfaces et les types abstraits ainsi que les types concrets qui implémentent ou étendent ces types.

Microsoft.Extensions.DependencyInjection facilite la création d’applications faiblement couplées et fournit toutes les fonctionnalités couramment trouvées dans les conteneurs d’injection de dépendances, notamment des méthodes pour inscrire les mappages de types et les instances d’objets, résoudre les objets, gérer les durées de vie des objets et injecter des objets dépendants dans les constructeurs des objets qu’il résout.