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 MainWindowViewModel
 2 {
 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 : Application
 2 {
 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 MainWindowViewModel
 2  {
 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

相关推荐
此wei浩亦5 小时前
WPF中使用 using prism.region 报错
c#·wpf·prism
dotent·1 天前
一个 WPF 文档和工具窗口布局容器
wpf
c#上位机1 天前
wpf之ComboBox
wpf
lindexi1 天前
WPF 引用 ASP.NET Core 的 AOT 版本
wpf·asp.netcore
我好喜欢你~2 天前
WPF---数据模版
wpf
hqwest3 天前
C#WPF实战出真汁07--【系统设置】--菜品类型设置
开发语言·c#·wpf·grid设计·stackpanel布局
hqwest3 天前
C#WPF实战出真汁08--【消费开单】--餐桌面板展示
c#·wpf·ui设计·wpf界面设计
orangapple3 天前
WPF 打印报告图片大小的自适应(含完整示例与详解)
c#·wpf
三千道应用题4 天前
WPF&C#超市管理系统(6)订单详情、顾客注册、商品销售排行查询和库存提示、LiveChat报表
开发语言·c#·wpf