当前位置: 首页 > news >正文

WPF MVVM进阶系列教程(三、使用依赖注入)

依赖注入(Dependency Injection, DI)

关于依赖注入,可以参考我前面写的一篇文章(比较杂乱,还没整理,先凑合着看吧。)

一文了解C#中的依赖注入(Dependency Injection) - zhaotianff - 博客园

DI的作用是将依赖进行解耦,帮助我们尽可能的编写可维护的代码,以便能实现高效地交付可运行的软件。

使用依赖注入对于WPF MVVM程序来说不是必须的,正如我在第一篇文章结尾处说的,引入一个框架的目的是为了更好的实现需求。

Unity Container

Unity Container是WPF MVVM开发中,使用较多的一种DI容器。

项目地址:

unitycontainer/unity: This repository contains all relevant information about Unity Container suit

案例演示

假设我们有如下需求

1、创建一个服务类,服务类中有一个显示消息的功能

2、当界面点击按钮时,调用这个服务类中的显示消息的功能

不使用DI容器

首先我们创建一个界面,在界面上放置一个按钮,并绑定命令。

MainWindow.xaml

 1 <Window x:Class="ShowMessageWithoutDI.MainWindow"2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"6         xmlns:local="clr-namespace:ShowMessageWithoutDI"7         mc:Ignorable="d"8         Title="MainWindow" Height="450" Width="800">9     <Grid>
10         <Button Content="Show Message" HorizontalAlignment="Center" VerticalAlignment="Center" Command="{Binding ShowMessageCommand}"></Button>
11     </Grid>
12 </Window>

创建服务类

IMyService.cs

1  internal interface IMyService
2  {
3      void ShowMessage();
4  }

MyService.cs

1 internal class MyService : IMyService
2 {
3     public void ShowMessage()
4     {
5         System.Windows.MessageBox.Show("HelloWorld");
6     }
7 }

创建ViewModel

MainWindowViewModel.cs

可以看到我们在ViewModel中创建了服务类的实例并创建了一个命令。

 1 public class MainWindowViewModel2 {3     public RelayCommand ShowMessageCommand { get; private set; }4 5     private IMyService service;6 7     public MainWindowViewModel()8     {9         service = new MyService();
10         ShowMessageCommand = new RelayCommand(ShowMessage);
11     }
12 
13     private void ShowMessage()
14     {
15         this.service.ShowMessage();
16     }
17 }

然后绑定ViewModel

MainWindow.xaml.cs

1  public partial class MainWindow : Window
2  {
3      public MainWindow()
4      {
5          InitializeComponent();
6 
7          this.DataContext = new MainWindowViewModel();
8      }
9  }

这里我们就会发现一个比较明显的问题,就是这个服务类需要在ViewModel中创建实例。

那么就会存在一个问题,其它ViewModel中需要使用时,应该怎么办?

那么就需要我们再次创建实例,或者将服务类定义成单例。

带着这个问题,我们看看使用DI后的情况

使用DI容器

下面我们将前面的代码转换为使用Unity Container的代码。

因为服务类和界面定义是一样的,这里就不再重复定义了。

首先我们引入Unity.Container nuget包。

然后在App.xaml.cs中,对容器进行初始化,并将服务类和ViewModel类注入到容器中。

 1 public partial class App : Application2 {3     public static IUnityContainer Container { get; private set; }4 5     protected override void OnStartup(StartupEventArgs e)6     {7         base.OnStartup(e);8 9         Container = new UnityContainer();
10         RegisterTypes(Container);
11     }
12 
13     private void RegisterTypes(IUnityContainer container)
14     {
15         //控制生命周期为单例
16         container.RegisterType<IMyService, MyService>(new ContainerControlledLifetimeManager());
17 
18         //默认
19         //container.RegisterType<MainWindowViewModel>();
20 
21         //指定参数注入
22         container.RegisterFactory<MainWindowViewModel>("ParameterViewModel", x => new MainWindowViewModel(x.Resolve<IMyService>()));
23     }
24 }

此时,我们就可以将MainWindowViewModel改造成如下模式的调用

 1  public class MainWindowViewModel2  {3      public RelayCommand ShowMessageCommand { get; private set; }4 5      private IMyService service;6 7      public MainWindowViewModel(IMyService myService)8      {9          this.service = myService;
10          ShowMessageCommand = new RelayCommand(ShowMessage);
11      }
12 
13      private void ShowMessage()
14      {
15          this.service.ShowMessage();
16      }
17  }

然后在MainWindow.xaml.cs中设置DataContext,跟前面有点区别的就是ViewModel实例是从容器中获取的。

1 public partial class MainWindow : Window
2 {
3     public MainWindow()
4     {
5         InitializeComponent();
6 
7         this.DataContext = App.Container.Resolve<MainWindowViewModel>();
8     }
9 }

刚接触的小伙伴可能会觉得奇怪,因为我们并没有创建MainWindowViewModel实例,而且也没有将IMyService以参数传入。

这里解释一下原因:

1、调用container.RegisterType();时,就会将MainWindowViewModel类型放到容器中。可以通过参数指定容器中对象的生命周期,可以参考Unity Container 对象生命周期的介绍。

2、调用Container.Resolve()时,就会创建一个MainWindowViewModel的实例。

3、容器在创建实例时,会自动判断构造函数中的类型是否在容器中,如果在容器中,就会创建一个实例(生命周期规则同上)并以参数形式传入。

当然也可以手动指定构造函数中的参数,手动指定代码会更清晰 

1  //指定参数注入
2  container.RegisterFactory<MainWindowViewModel>(x => new MainWindowViewModel(x.Resolve<IMyService>()));

这样改造以后,我们就可以在任意ViewModel中使用服务类。前面的问题也就解决了。并且ViewModel代码也会更加清晰 。

注意:虽然有不少的优点,但是也会有一些缺点,那就是代码整体结构变得复杂了,所以是否需要使用DI容器要根据自身的情况,框架的引入,是为了更好的解决问题,而不是带来问题。

ViewModelLocator

在使用容器的基础上,我们就可以使用ViewModelLocator模式将ViewModel的绑定进行简化。

首先我们建立一个ViewModelLocator

1 public class ViewModelLocator
2 {
3     public MainWindowViewModel MainWindowViewModel => App.Container.Resolve<MainWindowViewModel>();
4 }

然后在App.xaml中创建ViewModelLocator实例

 1 <Application x:Class="UseViewModelLocator.App"2              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"3              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"4              xmlns:local="clr-namespace:UseViewModelLocator"5              xmlns:viewmodel="clr-namespace:UseViewModelLocator.ViewModels"6              StartupUri="MainWindow.xaml">7     <Application.Resources>8         <viewmodel:ViewModelLocator x:Key="ViewModelLocator"></viewmodel:ViewModelLocator>9     </Application.Resources>
10 </Application>

最后在MainWindow.xaml中绑定ViewModel(下面代码中加粗的部分)

 1 <Window x:Class="UseViewModelLocator.MainWindow"2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"6         xmlns:local="clr-namespace:UseViewModelLocator"7         mc:Ignorable="d"8         Title="MainWindow" Height="450" Width="800" DataContext="{Binding Source={StaticResource ViewModelLocator},Path=MainWindowViewModel}">9     <Grid>
10         <Button Content="Show Message" HorizontalAlignment="Center" VerticalAlignment="Center" Command="{Binding ShowMessageCommand}"></Button>
11     </Grid>
12 </Window>

使用其它的DI容器

CommunityToolkit.Mvvm包中的DI容器使用,可以参考下面的链接

CommunityToolkit.Mvvm中的Ioc - zhaotianff - 博客园

示例代码

https://github.com/zhaotianff/WPF-MVVM-Beginner/tree/main/9_UseDI

http://www.dtcms.com/a/340197.html

相关文章:

  • 从 H.264/H.265 到 H.266:RTSP播放器的跨代际演进
  • Java开源工具Apache PDFBox(强大的处理 PDF文档工具:创建、读取、修改、解析和提取 PDF)
  • 【数据集】Argoverse 数据集:自动驾驶研究的强大基石
  • 06_并发编程高级特性
  • Jupyter Notebook 的终极进化:VS Code vs PyCharm,数据科学的IDE王者之争
  • 数据库优化提速(一)之进销存库存管理—仙盟创梦IDE
  • 仿真驱动的AI自动驾驶汽车安全设计与测试
  • (Python)[特殊字符] 基于Flask/FastAPI的RESTful API服务 + 数据库 + 缓存 + 简单前端 (Python项目)
  • 【报错】Please do not run this script with sudo bash
  • 自建开发工具IDE(一)之拖找排版—仙盟创梦IDE
  • 网络编程5(HTTPS)
  • CentOS7.9中安装Harbor以及配置https
  • STM32 定时器(级联实现32位定时器)
  • 机器学习数据预处理全流程:从缺失值处理到特征编码
  • Python 全栈开发常用命令
  • 安路EF2系列芯片单口ram ip核使用方法
  • 阿里通义千问Qwen-Long 快速文档解析
  • 【Git】执行命令时要求输入Username、Password
  • RabbitMQ:SpringAMQP Topic Exchange(主题交换机)
  • Flink双流join
  • 【黑客技术零基础入门】PHP环境搭建、安装Apache、安装与配置MySQL(非常详细)零基础入门到精通,收藏这一篇就够
  • (认识异常)
  • 建模工具Sparx EA的多视图协作教程
  • [系统架构设计师]面向服务架构设计理论与实践(十五)
  • Shader学习路线
  • C++ MFC/BCG编程:文件对话框(CFileDialog、CFolderPickerDialog)
  • 【免费AI文档助手开发实战系列】基于正则表达式的PDF脱敏python服务构建(一)
  • 国产化PDF处理控件Spire.PDF教程:如何使用 Python 添加水印到 PDF
  • 太阳光模拟器在无人机老化测试中的应用
  • JVM参数优化