Partage de technologie

WPF/C# : Comment implémenter l'injection de dépendances dans WPF

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Préface

Cet article utilise le projet WPF Gallery pour découvrir les concepts associés à l'injection de dépendances et comment effectuer l'injection de dépendances dans WPF.

Qu'est-ce que l'injection de dépendances

L'injection de dépendances (DI) est un modèle de conception utilisé pour mettre en œuvre le principe d'inversion de contrôle (IoC). L'objectif principal de l'injection de dépendances est de transférer la création d'objets et la gestion des dépendances entre objets depuis l'intérieur de l'objet vers un conteneur ou un framework externe, améliorant ainsi la maintenabilité, la testabilité et la flexibilité du code.

Le concept de base de l’injection de dépendances

  1. compter sur : Un objet a besoin d'un autre objet pour accomplir son travail, alors le premier dépend du second. Par exemple, une classe OrderService peut s'appuyer sur une classe ProductRepository pour obtenir des informations sur le produit.
  2. injection : Transmettez l'objet dépendant à l'objet qui en a besoin, au lieu de laisser l'objet qui en a besoin créer lui-même l'objet dépendant. L'injection peut être réalisée via des constructeurs, des propriétés ou des paramètres de méthode.
  3. récipient : Un framework ou une bibliothèque qui gère la création d'objets et les dépendances. Le conteneur est chargé d'instancier les objets, de résoudre les dépendances et d'injecter les objets dépendants dans les objets qui en ont besoin.

Types d'injection de dépendances

injection constructeur: L'objet dépendant est passé via le constructeur de la classe.

public class OrderService
{
    private readonly IProductRepository _productRepository;

    public OrderService(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Injection de propriété: Les objets dépendants sont transmis via les propriétés publiques de la classe.

public class OrderService
{
    public IProductRepository ProductRepository { get; set; }
}
  • 1
  • 2
  • 3
  • 4

méthode d'injection: L'objet dépendant est passé via le paramètre méthode de la classe.

public class OrderService
{
    public void ProcessOrder(IProductRepository productRepository)
    {
        // 使用 productRepository 处理订单
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Pourquoi l'injection de dépendances est nécessaire

L'injection de dépendances (DI) est un modèle de conception grâce auquel la création d'objets et la gestion des dépendances entre objets peuvent être transférées de l'intérieur de l'objet vers un conteneur ou un framework externe. Il existe plusieurs raisons et avantages importants pour l’injection de dépendances :

  1. Réduire l'accouplement : L'injection de dépendances transfère la gestion des dépendances de l'intérieur de l'objet vers le conteneur externe, de sorte que l'objet n'a pas besoin de savoir comment créer les objets dont il dépend, mais a seulement besoin de connaître l'interface de l'objet dépendant. Cela peut réduire considérablement le couplage entre les objets et rendre le code plus modulaire et flexible.
  2. Améliorer la testabilité : L'injection de dépendances rend les tests unitaires plus faciles et plus efficaces. En utilisant des objets fictifs ou des stubs au lieu d'objets dépendants réels, les développeurs peuvent effectuer des tests unitaires sans s'appuyer sur l'implémentation réelle. Cela permet de garantir l’indépendance et la fiabilité du test.
  3. Améliorer la maintenabilité : Puisque l'injection de dépendances réduit le couplage entre les objets, le code devient plus modulaire et plus clair. Cela rend le code plus facile à comprendre et à maintenir. Lorsque vous devez modifier ou remplacer un objet dépendant, il vous suffit de modifier les informations de configuration ou d'enregistrement, sans modifier le code qui utilise l'objet.
  4. Améliorer la flexibilité : L'injection de dépendances rend le système plus flexible et peut facilement remplacer les objets dépendants pour réaliser différentes fonctions ou comportements. Par exemple, différentes implémentations de couches d'accès à la base de données peuvent être commutées via des fichiers de configuration ou du code sans modifier le code de la logique métier.
  5. Promouvoir la séparation des préoccupations : L'injection de dépendances permet de réaliser une séparation des préoccupations (Separation of Concerns), de sorte que chaque objet n'a besoin de se concentrer que sur ses propres responsabilités, sans se soucier de la manière de créer et d'obtenir les objets dont il dépend. Cela contribue à améliorer la clarté et la maintenabilité du code.
  6. Prise en charge des modèles de conception et des meilleures pratiques : L'injection de dépendances est à la base de nombreux modèles de conception et bonnes pratiques, tels que l'inversion de contrôle (IoC), le modèle de localisateur de services, etc. En utilisant l'injection de dépendances, les développeurs peuvent plus facilement implémenter ces modèles et pratiques, améliorant ainsi la qualité et l'évolutivité du code.

Comment implémenter l'injection de dépendances

Cet article utilise le projet WPF Gallery pour apprendre à utiliser l'injection de dépendances dans l'adresse du code WPF :

https://github.com/microsoft/WPF-Samples/blob/main/SampleApplications/WPFGallery

Pour implémenter l'injection de dépendances dans ce projet, ces deux packages sont utilisés :

image-20240711100435001

Regardez d’abord le contenu de App.xaml.cs :

public partial class App : Application
{

    private static readonly IHost _host = Host.CreateDefaultBuilder()
        .ConfigureServices((context, services) =>
        {
            services.AddSingleton<INavigationService, NavigationService>();
            services.AddSingleton<MainWindow>();
            services.AddSingleton<MainWindowViewModel>();
            
            services.AddTransient<DashboardPage>();
            services.AddTransient<DashboardPageViewModel>();

            services.AddTransient<ButtonPage>();
            services.AddTransient<ButtonPageViewModel>();
            services.AddTransient<CheckBoxPage>();
            services.AddTransient<CheckBoxPageViewModel>();
            services.AddTransient<ComboBoxPage>();
            services.AddTransient<ComboBoxPageViewModel>();
            services.AddTransient<RadioButtonPage>();
            services.AddTransient<RadioButtonPageViewModel>();
            services.AddTransient<SliderPage>();
            services.AddTransient<SliderPageViewModel>();
            services.AddTransient<CalendarPage>();
            services.AddTransient<CalendarPageViewModel>();
            services.AddTransient<DatePickerPage>();
            services.AddTransient<DatePickerPageViewModel>();
            services.AddTransient<TabControlPage>();
            services.AddTransient<TabControlPageViewModel>();
            services.AddTransient<ProgressBarPage>();
            services.AddTransient<ProgressBarPageViewModel>();
            services.AddTransient<MenuPage>();
            services.AddTransient<MenuPageViewModel>();
            services.AddTransient<ToolTipPage>();
            services.AddTransient<ToolTipPageViewModel>();
            services.AddTransient<CanvasPage>();
            services.AddTransient<CanvasPageViewModel>();
            services.AddTransient<ExpanderPage>();
            services.AddTransient<ExpanderPageViewModel>();
            services.AddTransient<ImagePage>();
            services.AddTransient<ImagePageViewModel>();
            services.AddTransient<DataGridPage>();
            services.AddTransient<DataGridPageViewModel>();
            services.AddTransient<ListBoxPage>();
            services.AddTransient<ListBoxPageViewModel>();
            services.AddTransient<ListViewPage>();
            services.AddTransient<ListViewPageViewModel>();
            services.AddTransient<TreeViewPage>();
            services.AddTransient<TreeViewPageViewModel>();
            services.AddTransient<LabelPage>();
            services.AddTransient<LabelPageViewModel>();
            services.AddTransient<TextBoxPage>();
            services.AddTransient<TextBoxPageViewModel>();
            services.AddTransient<TextBlockPage>();
            services.AddTransient<TextBlockPageViewModel>();
            services.AddTransient<RichTextEditPage>();
            services.AddTransient<RichTextEditPageViewModel>();
            services.AddTransient<PasswordBoxPage>();
            services.AddTransient<PasswordBoxPageViewModel>();
            services.AddTransient<ColorsPage>();
            services.AddTransient<ColorsPageViewModel>();

            services.AddTransient<LayoutPage>();
            services.AddTransient<LayoutPageViewModel>();
            services.AddTransient<AllSamplesPage>();
            services.AddTransient<AllSamplesPageViewModel>();
            services.AddTransient<BasicInputPage>();
            services.AddTransient<BasicInputPageViewModel>();
            services.AddTransient<CollectionsPage>();
            services.AddTransient<CollectionsPageViewModel>();
            services.AddTransient<MediaPage>();
            services.AddTransient<MediaPageViewModel>();
            services.AddTransient<NavigationPage>();
            services.AddTransient<NavigationPageViewModel>();
            services.AddTransient<TextPage>();
            services.AddTransient<TextPageViewModel>();
            services.AddTransient<DateAndTimePage>();
            services.AddTransient<DateAndTimePageViewModel>();
            services.AddTransient<StatusAndInfoPage>();
            services.AddTransient<StatusAndInfoPageViewModel>();
            services.AddTransient<SamplesPage>();
            services.AddTransient<SamplesPageViewModel>();
            services.AddTransient<DesignGuidancePage>();
            services.AddTransient<DesignGuidancePageViewModel>();

            services.AddTransient<UserDashboardPage>();
            services.AddTransient<UserDashboardPageViewModel>();

            services.AddTransient<TypographyPage>();
            services.AddTransient<TypographyPageViewModel>();

            services.AddSingleton<IconsPage>();
            services.AddSingleton<IconsPageViewModel>();

            services.AddSingleton<SettingsPage>();
            services.AddSingleton<SettingsPageViewModel>();

            services.AddSingleton<AboutPage>();
            services.AddSingleton<AboutPageViewModel>();
        }).Build();


    [STAThread]
    public static void Main()
    {
        _host.Start();

        App app = new();
        app.InitializeComponent();
        app.MainWindow = _host.Services.GetRequiredService<MainWindow>();
        app.MainWindow.Visibility = Visibility.Visible;
        app.Run();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114

image-20240711083011393

Qu’est-ce qu’IHost ?

En C#,IHost est une interface utilisée dans .NET pour créer et configurer des applicationsHostrésumé conceptuel.IHost Une interface définit un ensemble de services et de composants requis pour démarrer, exécuter et gérer une application. Il est généralement utilisé avec les applications ASP.NET Core, mais fonctionne également avec d'autres types d'applications .NET, telles que les applications console ou les programmes WPF.

image-20240711082817268

IHostL'interface se compose deHostBuilderImplémentation de classe, qui fournit la création et la configuration deIHostméthodes d'instance.HostBuilderVous permet d'ajouter divers services tels que la journalisation, la configuration, les conteneurs d'injection de dépendances, etc., et de configurer le comportement de démarrage et d'arrêt de votre application.

image-20240711083048854

image-20240711083156306

Fournit des méthodes pratiques pour créer des instances Microsoft.Extensions.Hosting.IHostBuilder avec des valeurs par défaut préconfigurées.

image-20240711083713204

Renvoie un IHostBuilder.

image-20240711084145756

image-20240711084211035

Ajoutez des services au conteneur. Cette opération peut être appelée plusieurs fois et ses résultats sont cumulatifs.

La signification du paramètre configureDelegate est de configurer le délégué de Microsoft.Extensions.DependencyInjection.IServiceCollection.
Cette collection sera utilisée pour construire System.IServiceProvider.

Le délégué nécessite deux types de paramètres : HostBuilderContext et IServiceCollection, qui n'ont aucune valeur de retour.

image-20240711084853971

Une expression Lambda qui satisfait le type de délégué est transmise ici.

En C#,() => {} Est une syntaxe pour les expressions Lambda. Les expressions Lambda sont un wrapper de délégué léger qui vous permet de définir une méthode anonyme et de la transmettre en tant que paramètre à une méthode prenant en charge les délégués ou les arborescences d'expressions.

Les expressions Lambda fournissent un moyen concis de définir des méthodes, et elles sont particulièrement utiles lorsque vous devez transmettre des méthodes en tant que paramètres à d'autres méthodes.

image-20240711085344696

Lors de l'ajout de services, il existe ici deux cycles de vie, en plus de AddSingleton et AddTransient, il existe également AddScoped.

Ces méthodes définissent le cycle de vie du service, c'est-à-dire la manière dont les instances de service sont créées et gérées dans l'application.

AjouterSingleton

  • cycle de vie:Singleton
  • signification : Une seule instance de service est créée pendant tout le cycle de vie de l'application. Peu importe le nombre de fois que vous le demandez au conteneur, la même instance sera renvoyée.
  • Scène applicable: convient aux services sans état ou aux ressources partagées dans l'ensemble de l'application, telles que les configurations, les enregistreurs, etc.

AjouterTransient

  • cycle de vie: Transitoire
  • signification: Chaque fois qu'un service est demandé au conteneur, une nouvelle instance est créée.
  • Scène applicable: convient aux services avec état ou aux scénarios qui nécessitent une nouvelle instance pour chaque requête, tels que des pages, des modèles de vue, etc.

AjouterScoped

  • cycle de vie: Portée
  • signification : Au sein de chaque étendue, les instances de service sont uniques. Les portées sont souvent associées au cycle de vie d'une requête, par exemple dans les applications Web, une nouvelle portée est créée à chaque requête HTTP.
  • Scène applicable: convient aux services qui doivent partager des instances dans la portée de la requête, tels que les contextes de base de données.

utiliser ces services

Dans la fonction Principale :

image-20240711095100016

démarrer_host,passer_host.Services.GetRequiredService<MainWindow>();Obtenez l'instance MainWindow.

En prenant la classe MainWindow comme exemple, affichez le constructeur de MainWindow dans MainWindow.xaml.cs :

public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider, INavigationService navigationService)
{
    _serviceProvider = serviceProvider;
    ViewModel = viewModel;
    DataContext = this;
    InitializeComponent();

    Toggle_TitleButtonVisibility();

    _navigationService = navigationService;
    _navigationService.Navigating += OnNavigating;
    _navigationService.SetFrame(this.RootContentFrame);
    _navigationService.Navigate(typeof(DashboardPage));

    WindowChrome.SetWindowChrome(
        this,
        new WindowChrome
        {
            CaptionHeight = 50,
            CornerRadius = default,
            GlassFrameThickness = new Thickness(-1),
            ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4),
            UseAeroCaptionButtons = true
        }
    );

    this.StateChanged += MainWindow_StateChanged;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Après avoir supprimé le contenu sans rapport avec ce sujet, le résultat est le suivant :

public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider, INavigationService navigationService)
{
    _serviceProvider = serviceProvider;
    ViewModel = viewModel; 
    _navigationService = navigationService;  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Avez-vous remarqué que vous n'avez plus besoin de créer ces objets vous-même ? La création de ces objets est gérée par le conteneur d'injection de dépendances. Lorsque ces objets sont nécessaires, ils peuvent être injectés via le constructeur comme maintenant.

Si l’injection de dépendances n’est pas utilisée, cela pourrait ressembler à ceci :

public MainWindow()
{
    _serviceProvider = new IServiceProvider();
    ViewModel = new MainWindowViewModel(); 
    _navigationService = new INavigationService();  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Résumer

Cet article présente d'abord le concept d'injection de dépendances, puis explique pourquoi l'injection de dépendances est nécessaire et apprend enfin comment utiliser l'injection de dépendances dans WPF via le projet WPF Gallery.

faire référence à

1. [Exemples WPF/Exemples d'applications/WPFGallery sur le site principal · microsoft/Exemples WPF (github.com)](https://github.com/microsoft/WPF-Samples/tree/main/Exemples d'applications/WPFGallery)