WPF Prism中的依赖注入详解

依赖注入

Prism 内置了对依赖注入的支持,默认使用UnityDryIoc作为 DI 容器。其核心思想是:

  • 将服务的创建和管理交给 DI 容器,而非手动new
  • 通过构造函数注入、属性注入等方式获取依赖
  • 支持服务的生命周期管理(Transient、Singleton、Scoped

本系列教程以Unity作为DI容器进行演示(DryIoc除了配置和注入时有点小区别,使用方面无异)

如果对依赖注入还不够了解,可以参考前面的文章

https://www.cnblogs.com/zhaotianff/p/18884247

https://www.cnblogs.com/zhaotianff/p/18515397

如何配置不同的DI容器

DryIoc

1、安装Prism.Wpf包和Prism.DryIoc

2、修改App.xaml,将App类替换为Prism.DryIoc.PrismApplication类并移除StartupUri

复制代码
1 <prism:PrismApplication x:Class="_12_Prism_Ioc.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:_12_Prism_Ioc"
5              xmlns:prism="http://prismlibrary.com/">
6     <Application.Resources>
7          
8     </Application.Resources>
9 </prism:PrismApplication>

3、改造App类,使之继承自Prism.DryIoc.PrismApplication

改造后的App类和前面介绍的Bootstrapper类结构一致

4、在RegisterTypes函数中注册类型到容器中

示例代码如下:

复制代码
 1  public partial class App : PrismApplication
 2  {
 3      // 1. 配置主窗口(Prism 启动时加载)
 4      protected override Window CreateShell()
 5      {
 6          // DryIoc 会自动解析 MainWindow(依赖注入)
 7          return Container.Resolve<MainWindow>();
 8      }
 9 
10      // 2. 注册服务到 DryIoc 容器
11      protected override void RegisterTypes(IContainerRegistry containerRegistry)
12      {
13          // ========== 基础注册方式 ==========
14          // 单例注册(全局唯一)
15          containerRegistry.RegisterSingleton<IMessageService, MessageService>();
16 
17          // 瞬时注册(每次解析新建实例)
18          containerRegistry.Register<IDataService, DataService>();
19 
20          // 若需要使用 DryIoc 原生 API,可通过 Container 转换
21          var dryIocContainer = containerRegistry.GetContainer();
22 
23          //dryIocContainer是DryIoc原生容器对象,可以参考DryIoc文档了解详细使用
24          //https://github.com/dadhi/DryIoc
25 
26          // 注册视图(Prism 导航用)
27          containerRegistry.RegisterForNavigation<HomeView>();
28      }
29 
30      // 3. 可选:模块化配置(若使用模块)
31      protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
32      {
33          base.ConfigureModuleCatalog(moduleCatalog);
34          // 注册模块(示例)
35          // moduleCatalog.AddModule<MyModule>();
36      }
37  }

Unity

1、安装Prism.Wpf包和Prism.Unity

2、创建Bootstrapper类

复制代码
 1   public class Bootstrapper : PrismBootstrapper
 2   {
 3       protected override DependencyObject CreateShell()
 4       {
 5           return Container.Resolve<MainWindow>();
 6       }
 7 
 8       protected override void RegisterTypes(IContainerRegistry containerRegistry)
 9       {
10           
11       }
12   }

3、移除App.xaml中的StartupUri

4、修改App类,重写Startup函数,启动Bootstrapper

复制代码
 1  public partial class App : Application
 2  {
 3      protected override void OnStartup(StartupEventArgs e)
 4      {
 5          base.OnStartup(e);
 6 
 7          Bootstrapper bootstrapper = new Bootstrapper();
 8          bootstrapper.Run();
 9      }
10  }

Prism框架提供的服务

在前面介绍Bootstrapper的职责时,其中包含了一项自动初始化 Prism 的关键服务。

Prism框架为我们提供了以下服务

IRegionManager:管理视图区域(Region),实现视图的动态加载 / 切换;

IEventAggregator:实现模块间的无耦合通信;

IDialogService:统一管理对话框;

INavigationService:实现视图导航。

Bootstrapper初始化时,框架会帮我们注入这些服务的实例到容器中。

后面我们在使用时,就可以直接从容器中去取。

这里暂时不做进一步演示,等介绍到对应的服务时,再进行演示。

如何注册自己的服务 / 视图

首先我们创建服务接口/类

复制代码
 1 // 服务接口
 2 public interface IMessageService
 3 {
 4     string GetMessage();
 5 }
 6 
 7 // 服务实现
 8 public class MessageService : IMessageService
 9 {
10      public string GetMessage()
11      {
12          return "Hello Prism DI!";
13      }
14  }
15  
16  // 服务实现2
17 public class MessageService2 : IMessageService
18 {
19     public string GetMessage()
20     {
21         return "Hello Prism DI 2222222!";
22     }
23 }

然后在RegisterTypes函数中进行注册

复制代码
1  protected override void RegisterTypes(IContainerRegistry containerRegistry)
2  {
3      containerRegistry.Register<IMessageService, MessageService>();
4  }

Prism提供了三个注册类型的接口

RegisterTransient,每次service请求都是获得不同的实例.

RegisterScoped对于同一个请求返回同一个实例,不同的请求返回不同的实例.

RegisterSingleton每次都是获得同一个实例, 单一实例模式.

此外,Prism还提供一种注册视图与 ViewModel(自动关联)的方式

这种方式在后面进行导航时会经常用到

复制代码
1 protected override void RegisterTypes(IContainerRegistry containerRegistry)
2 {
3      containerRegistry.RegisterForNavigation<ViewA, ViewAViewModel>();
4 }

如何使用Unity的原生接口

通过下面的语句,就可以获取Unity原生容器对象(DryIoc也是一样的方法)

复制代码
1 var unityContainer = containerProvider.GetContainer()

Unity原生的注册方式会有点小区别,可以参考Unity的文档(https://github.com/UnityContainer/Unity)

复制代码
1 unityContainer.RegisterType<IMessageService, MessageService>("Service1", new Unity.Lifetime.ContainerControlledLifetimeManager());

说明:Prism 的 IContainerRegistry 是 DI容器的封装,RegisterTypes 是注册服务的唯一推荐入口,不要在其他地方随意创建 DI容器实例。

如何注册同一个接口的不同实现

在前面我们实现了MessageServiceMessageService2,但是只注册了MessageService

如果要注册同一个接口的不同实现,我们可以通过给实例命名的方式实现

复制代码
1  containerRegistry.Register<IMessageService, MessageService>("ServiceA");
2  containerRegistry.Register<IMessageService, MessageService2>("ServiceB");

如何使用容器中注入的对象

在前面我们介绍CommunityToolkit中的Ioc时,是使用了手动从容器中去取的方法。

类似下面的形式

复制代码
1  var messageService = containerRegistry.GetContainer().Resolve<IMessageService>();

在Prism中,推荐是使用构造函数注入的形式来获取容器中的对象,如下所示

复制代码
 1  public class MainWindowViewModel : BindableBase
 2     {
 3         private string _message;
 4         public string Message
 5         {
 6             get => _message;
 7             set => SetProperty(ref _message, value);
 8         }
 9 
10         // 构造函数注入(Unity自动解析)
11         public MainWindowViewModel(IMessageService messageService)
12         {
13             Message = messageService.GetMessage();
14         }
15     }

但是这种方式是有前提的,需要使用ViewModelLocator.AutoWireViewModel附加属性,将ViewModel自动绑定到View上。

当指定了ViewModelLocator.AutoWireViewModel=true时,Prism会帮我们自动将ViewModelView进行绑定,这个功能在下一篇文章中会进行详细介绍。

注意:我们也可以把IContainerProvider进行注入,以便手动去容器中获取对象实例,如下所示。

但是不推荐这种方法,尽量使用构造函数自动注入的方式。

其中:IContainerProvider是Unity提供的解析接口,用于从容器中获取已注册的服务实例。

复制代码
1  public MainWindowViewModel(IContainerProvider containerProvider)
2  {
3      //手动解析
4      var messageServiceB = containerProvider.Resolve<IMessageService>("ServiceB");
5  }

如何指定注入的对象

在前面我们使用构造函数来获取注入的对象,那这种情况是系统控制的。如果我们使用的接口有两个实现,那如何获取指定的实例呢?

解决方法就是手动配置构造函数注入

这种情况需要使用Unity的原生接口,如下所示

复制代码
1  protected override void RegisterTypes(IContainerRegistry containerRegistry)
2  {
3      //注册同一接口的不同实现
4      containerRegistry.Register<IMessageService, MessageService>("ServiceA");
5      containerRegistry.Register<IMessageService, MessageService2>("ServiceB");
6 
7      //手动配置注入
8      containerRegistry.GetContainer().RegisterFactory<MainWindowViewModel>((container, type, name) => new MainWindowViewModel(container.Resolve<IMessageService>("ServiceB")));
9  }

这样我们在MainWindowViewModel中使用时,获取的就是MessageService2的实例

复制代码
 1  public class MainWindowViewModel : BindableBase
 2  {
 3      private IMessageService messageService;
 4 
 5      public MainWindowViewModel(IMessageService messageService)
 6      {
 7          this.messageService = messageService;
 8 
 9          System.Windows.MessageBox.Show( messageService.GetMessage());
10      }
11  }

运行效果

示例代码

https://github.com/zhaotianff/WPF-MVVM-Beginner/tree/main/12_Prism_Ioc

相关推荐
△曉風殘月〆2 小时前
WPF Prism创建Bootstrapper/启动器
wpf·mvvm
小曹要微笑2 小时前
WPF的依赖与附加属性
wpf·依赖属性·附加属性
bugcome_com1 天前
WPF 命令 ICommand 从原理到实战
后端·wpf·icommand
武藤一雄2 天前
WPF处理耗时操作的7种方法
microsoft·c#·.net·wpf
Venom842 天前
我的 WPF Powermill 工具
wpf
一念春风3 天前
证件照制作工具(WPF C#)
c#·wpf
He BianGu4 天前
【笔记】在WPF中GiveFeedbackEventHandler的功能和应用场景详细介绍
笔记·wpf
就是有点傻4 天前
WPF自定义控件-水晶球
wpf
He BianGu4 天前
【笔记】在WPF中QueryContinueDragEvent的详细介绍
笔记·wpf