Compartilhamento de tecnologia

WPF/C#: Como implementar injeção de dependência no WPF

2024-07-12

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

Prefácio

Este artigo usa o projeto Galeria WPF para aprender os conceitos relacionados à injeção de dependência e como executar a injeção de dependência no WPF.

O que é injeção de dependência

Injeção de Dependência (DI) é um padrão de design usado para implementar o princípio de Inversão de Controle (IoC). O principal objetivo da injeção de dependência é transferir a criação de objetos e o gerenciamento de dependências entre objetos de dentro do objeto para um contêiner ou estrutura externa, melhorando assim a capacidade de manutenção, testabilidade e flexibilidade do código.

O conceito central de injeção de dependência

  1. confiar : Um objeto requer outro objeto para completar seu trabalho, então o primeiro depende do último. Por exemplo, uma classe OrderService pode contar com uma classe ProductRepository para obter informações sobre o produto.
  2. injeção : passe o objeto dependente para o objeto que precisa dele, em vez de deixar o objeto que precisa dele criar o próprio objeto dependente. A injeção pode ser obtida por meio de construtores, propriedades ou parâmetros de método.
  3. recipiente : uma estrutura ou biblioteca que gerencia a criação e dependências de objetos. O contêiner é responsável por instanciar objetos, resolver dependências e injetar objetos dependentes em objetos que os necessitam.

Tipos de injeção de dependência

injeção de construtor: O objeto dependente é passado pelo construtor da classe.

public class OrderService
{
    private readonly IProductRepository _productRepository;

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

Injeção de propriedade: objetos dependentes são passados ​​pelas propriedades públicas da classe.

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

injeção de método: O objeto dependente é passado pelo parâmetro do método da classe.

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

Por que a injeção de dependência é necessária

Injeção de Dependência (DI) é um padrão de design por meio do qual a criação de objetos e o gerenciamento de dependências entre objetos podem ser transferidos de dentro do objeto para um contêiner ou estrutura externa. Existem vários motivos e vantagens importantes para a injeção de dependência:

  1. Reduzir o acoplamento : A injeção de dependência transfere o gerenciamento de dependências de dentro do objeto para o contêiner externo, de forma que o objeto não precise saber como criar os objetos dos quais depende, mas apenas precise conhecer a interface do objeto dependente. Isto pode reduzir significativamente o acoplamento entre objetos e tornar o código mais modular e flexível.
  2. Melhore a testabilidade : A injeção de dependência torna o teste de unidade mais fácil e eficiente. Ao usar objetos simulados ou stubs em vez de objetos dependentes reais, os desenvolvedores podem realizar testes unitários sem depender da implementação real. Isso ajuda a garantir a independência e a confiabilidade do teste.
  3. Melhore a capacidade de manutenção : Como a injeção de dependência reduz o acoplamento entre objetos, o código se torna mais modular e claro. Isso torna o código mais fácil de entender e manter. Quando precisar modificar ou substituir um objeto dependente, basta modificar as informações de configuração ou registro, sem modificar o código que utiliza o objeto.
  4. Melhorar a flexibilidade : A injeção de dependência torna o sistema mais flexível e pode facilmente substituir objetos dependentes para alcançar diferentes funções ou comportamentos. Por exemplo, diferentes implementações da camada de acesso ao banco de dados podem ser alternadas por meio de arquivos de configuração ou código sem modificar o código da lógica de negócios.
  5. Promova a separação de preocupações : A injeção de dependência ajuda a conseguir a separação de interesses (Separação de Preocupações), de forma que cada objeto precise apenas se concentrar em suas próprias responsabilidades, sem se preocupar em como criar e obter os objetos dos quais depende. Isso ajuda a melhorar a clareza e a capacidade de manutenção do código.
  6. Apoie padrões de design e práticas recomendadas : A injeção de dependência é a base de muitos padrões de design e práticas recomendadas, como Inversão de Controle (IoC), Padrão Localizador de Serviço, etc. Ao usar a injeção de dependência, os desenvolvedores podem implementar mais facilmente esses padrões e práticas, melhorando assim a qualidade e a escalabilidade do código.

Como implementar injeção de dependência

Este artigo usa o projeto Galeria WPF para aprender como usar injeção de dependência no endereço de código WPF:

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

Para implementar a injeção de dependência neste projeto, estes dois pacotes são usados:

imagem-20240711100435001

Primeiro, observe o conteúdo 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

imagem-20240711083011393

O que é IHost?

Em C#,IHost é uma interface usada em .NET para construir e configurar aplicativosHostconceito abstrato.IHost Uma interface define uma coleção de serviços e componentes necessários para iniciar, executar e gerenciar um aplicativo. Normalmente é usado com aplicativos ASP.NET Core, mas também funciona com outros tipos de aplicativos .NET, como aplicativos de console ou programas WPF.

imagem-20240711082817268

IHostA interface consiste emHostBuilderImplementação de classe, que fornece a criação e configuração deIHostmétodos de instância.HostBuilderPermite adicionar vários serviços, como registro, configuração, contêineres de injeção de dependência, etc., e configurar o comportamento de inicialização e parada de seu aplicativo.

imagem-20240711083048854

imagem-20240711083156306

Fornece métodos convenientes para criar instâncias Microsoft.Extensions.Hosting.IHostBuilder com padrões pré-configurados.

imagem-20240711083713204

Retorna um IHostBuilder.

imagem-20240711084145756

imagem-20240711084211035

Adicione serviços ao contêiner. Esta operação pode ser chamada diversas vezes e seus resultados são cumulativos.

O significado do parâmetro configureDelegate é configurar o delegado de Microsoft.Extensions.DependencyInjection.IServiceCollection.
Esta coleção será usada para construir System.IServiceProvider.

O delegado requer dois tipos de parâmetros: HostBuilderContext e IServiceCollection, que não possuem valor de retorno.

imagem-20240711084853971

Uma expressão Lambda que satisfaça o tipo delegado é passada aqui.

Em C#,() => {} É uma sintaxe para expressões Lambda. As expressões lambda são um wrapper de delegado leve que permite definir um método anônimo e passá-lo como parâmetro para um método que oferece suporte a delegados ou árvores de expressão.

As expressões lambda fornecem uma maneira concisa de definir métodos e são úteis especialmente quando você precisa passar métodos como parâmetros para outros métodos.

imagem-20240711085344696

Ao adicionar serviços, existem dois ciclos de vida aqui, além de AddSingleton e AddTransient, há também AddScoped.

Esses métodos definem o ciclo de vida do serviço, ou seja, como as instâncias de serviço são criadas e gerenciadas na aplicação.

Adicionar Singleton

  • vida útil:Singleton
  • significado : apenas uma instância de serviço é criada durante todo o ciclo de vida do aplicativo. Não importa quantas vezes você solicite do contêiner, a mesma instância será retornada.
  • Cena aplicável: Adequado para serviços sem estado ou recursos compartilhados por todo o aplicativo, como configurações, registradores, etc.

Adicionar Transiente

  • vida útil: Transitório
  • significado: cada vez que um serviço é solicitado do contêiner, uma nova instância é criada.
  • Cena aplicável: Adequado para serviços com estado ou cenários que exigem uma nova instância para cada solicitação, como páginas, modelos de visualização, etc.

Adicionar escopo

  • vida útil: Escopo
  • significado : dentro de cada escopo, as instâncias de serviço são exclusivas. Os escopos são frequentemente associados ao ciclo de vida de uma solicitação; por exemplo, em aplicativos da Web, um novo escopo é criado a cada solicitação HTTP.
  • Cena aplicável: adequado para serviços que precisam compartilhar instâncias dentro do escopo da solicitação, como contextos de banco de dados.

use esses serviços

Na função principal:

imagem-20240711095100016

comece_host,passar_host.Services.GetRequiredService<MainWindow>();Obtenha a instância MainWindow.

Tomando a classe MainWindow como exemplo, visualize o construtor de MainWindow em 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

Após a remoção do conteúdo irrelevante para este tópico, fica o seguinte:

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

Você notou que não precisa mais criar esses objetos sozinho. A criação desses objetos é gerenciada pelo contêiner de injeção de dependência. Quando esses objetos são necessários, eles podem ser injetados através do construtor como agora.

Se a injeção de dependência não for usada, poderá ficar assim:

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

Resumir

Este artigo primeiro apresenta o conceito de injeção de dependência, depois explica por que a injeção de dependência é necessária e, finalmente, aprende como usar a injeção de dependência no WPF por meio do projeto Galeria WPF.

referir-se

1、[Amostras WPF/Aplicativos de amostra/WPFGallery na página principal · microsoft/WPF-Samples (github.com)](https://github.com/microsoft/WPF-Samples/tree/main/Aplicativos de amostra/WPFGallery)