本文章使用的Prism、Prism.Unity版本:7.2.0.1422
一、使用Prism.Unity構建一個Prism應用
需要說明的是:老版本的Prism,構建WPF應用是新建一個類,繼承自UnityBootstrapper。但是新版本的已經不建議這么做了,而是App類直接繼承自PrismApplication,具體可以查看新版本中對UnitBootstrapper的注釋說明:
line 28行:

1.新建一個WPF應用
.NET版本選擇最高版4.7.2.
2.在Nuget中添加prism.unity

選擇Prism.Unity進行安裝,安裝過程中,會彈出如下界面:

說明Prism.Unity直接或間接依賴了這么多的包,其中:
- Prism.Core
- Prism.Wpf
- Unity.Container
這三個包是必須要了解的
3.在Nuget中添加Unity.Configuration
這個包是當我們需要通過配置文件來實現容器注入時需要用到的

4.修改App.Xaml:
<prism:PrismApplication x:Class="SimplePrismAppTest.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SimplePrismAppTest" xmlns:prism="http://prismlibrary.com/" > <Application.Resources> </Application.Resources> </prism:PrismApplication>
這里引入了xmlns:prism="http://prismlibrary.com/"的空間,然后使用了PrsimApplication
5.修改App.cs
/// <summary> /// App.xaml 的交互邏輯 /// </summary> public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } }
這里要引入Prism.Ioc和Prism.Unity兩個命名空間
這樣我們的第一個Prism引用程序就搭建好了
6.PrsimApplication分析
平時我們啟動的App是繼承自System.Windows下的Application類
我們將PrismApplication轉到定義發現是這樣的一種繼承關系:

PrismApplicationBase中定義了兩個三個抽象方法和若干個虛方法:
/// <summary> /// Creates the container used by Prism. /// </summary> /// <returns>The container</returns> protected abstract IContainerExtension CreateContainerExtension(); /// <summary> /// Used to register types with the container that will be used by your application. /// </summary> protected abstract void RegisterTypes(IContainerRegistry containerRegistry); /// <summary> /// Creates the shell or main window of the application. /// </summary> /// <returns>The shell of the application.</returns> protected abstract Window CreateShell();
其中CreateContainerExtension方法被PrismApplication實現了:
protected override IContainerExtension CreateContainerExtension() { return new UnityContainerExtension(); }
所以App繼承PrismApplication后,必須實現另外兩個抽象方法:RegisterTypes()和CreateShell()
- RegisterTypes():程序啟動時需要注入的類型
- CreateShell():程序啟動時,需要啟動的主窗體
二、Prism.Unity的注入
在了解注入之前,我們首先了解一下幾個接口及類的關系,他們是:
- IContainerProvider:抽象第三方IOC框架從容器中拿對象的方法
- IContainerRegistry:抽象第三方IOC框架注冊對象、類型到容器的方法
- IContainerExtension:將注冊對象、取對象統一的接口
IContainerExtension<T>:注冊對象、取對象的泛型接口- UnityContainerExtension:第三方框架Unity整合到Prism的實現方式,運用了適配器模式將Unity整合到Prism中
- IUnityContainer:Unity中自己容器的接口,Unity有自己去實現
由以上得出的結論是:
- 取對象用IContainerProvider類型
- 注冊對象用IContainerRegistry類型
- IContainerExtension類型既可以注冊對象,也可以取對象
關系如下圖所示:

代碼方式的注入,我們只能通過構造函數去注入
1.注入Prism.Wpf已有的類型
上面在分析PrismApplication的時候,PrismApplicationBase類有重寫OnStartup方法。查看源碼,我們發現OnStartup方法里調用了RegisterRequiredTypes這個方法,這個方法如下:
源碼地址:https://github.com/PrismLibrary/Prism/blob/master/Source/Wpf/Prism.Wpf/PrismApplicationBase.cs
line 116行
/// <summary> /// Registers all types that are required by Prism to function with the container. /// </summary> /// <param name="containerRegistry"></param> protected virtual void RegisterRequiredTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterInstance(_containerExtension); containerRegistry.RegisterInstance(_moduleCatalog); containerRegistry.RegisterSingleton<ILoggerFacade, TextLogger>(); containerRegistry.RegisterSingleton<IDialogService, DialogService>(); containerRegistry.RegisterSingleton<IModuleInitializer, ModuleInitializer>(); containerRegistry.RegisterSingleton<IModuleManager, ModuleManager>(); containerRegistry.RegisterSingleton<RegionAdapterMappings>(); containerRegistry.RegisterSingleton<IRegionManager, RegionManager>(); containerRegistry.RegisterSingleton<IEventAggregator, EventAggregator>(); containerRegistry.RegisterSingleton<IRegionViewRegistry, RegionViewRegistry>(); containerRegistry.RegisterSingleton<IRegionBehaviorFactory, RegionBehaviorFactory>(); containerRegistry.Register<IRegionNavigationJournalEntry, RegionNavigationJournalEntry>(); containerRegistry.Register<IRegionNavigationJournal, RegionNavigationJournal>(); containerRegistry.Register<IRegionNavigationService, RegionNavigationService>(); containerRegistry.Register<IDialogWindow, DialogWindow>(); //default dialog host }
我們關注常用的幾個類型:
- IContainerExtension
- IMoudleManager
- IRegionManager
- IEventAggregator
這樣,我們通過Resolve獲取到的對象,都可以將以上已經注冊在容器里的對象直接通過構造函數注入進去。
例如,App.cs中創建主窗體時:
protected override Window CreateShell() { return Container.Resolve<MainWindow>(); }
通過Resolve獲取到的對象,我們就可以在MainWindow的構造函數的方法參數中,添加已經注冊到容器中的任意個數類型對象,這些類型都將自動注入到MainWindow中
我們可以將IContainerExtension類型的容器繼續注入到MainWindow中
public MainWindow(IContainerExtension container) { InitializeComponent(); }
也可以這樣注入(注入的參數順序可以任意):
public partial class MainWindow : Window { private IContainerExtension _container; private IModuleManager _moudleManager; private IRegionManager _regionManager; private IEventAggregator _eventAggregator; public MainWindow(IContainerExtension container, IModuleManager moudleManager,IRegionManager regionManager, IEventAggregator eventAggregator) { InitializeComponent(); this._container = container; this._moudleManager = moudleManager; this._regionManager = regionManager; this._eventAggregator = eventAggregator; } }
2.注入自定義類型
首先我們定義三個類和一個接口
接口:
public interface IPerson { string Sex { get; } string Name { get; set; } }
兩個實現類:
public class Man : IPerson { public string Sex => "男"; public string Name { get; set; } } public class Woman : IPerson { public string Sex => "女"; public string Name { get; set; } }
一個動物類,聚合IPerson
public class Animal { /// <summary> /// 動物的主人 /// </summary> public IPerson BelongPerson; public Animal(IPerson owner) { this.BelongPerson = owner; } }
1.通過接口類型來注冊:
在App.cs中的RegisterTypes方法中,寫入如下代碼:
protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.Register<IPerson, Man>(); }
這樣,我們任何時候,通過容器Resolve得到的IPerson類型都是Man類型:
public MainWindow(IContainerExtension container) { InitializeComponent(); this._container = container; IPerson person = container.Resolve<IPerson>();//man }
需要注意的是,Register<TFrom,TTo>注冊的是非單例的對象,也就是每次Resolve的時候,容器每次幫我們創建了一個新對象。如果需要容器每次給我們的是同一個對象,就需要用RegisterSingleton<TFrom, TTo>:
protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterSingleton<IPerson, Man>(); }
取出單例時:
public MainWindow(IContainerExtension container) { InitializeComponent(); this._container = container; IPerson person1 = container.Resolve<IPerson>();//man IPerson person2 = container.Resolve<IPerson>();//man bool result=person1==person2//person1和person2是同一個對象 }
以上兩種類型的注冊,均可以按照名稱來注冊這個類型,比如注冊非單例時,可以這樣:
protected override void RegisterTypes(IContainerRegistry containerRegistry) { IPerson p1= containerRegistry.Register<IPerson, Man>("man"); }
獲取這個對象類型時:
public MainWindow(IContainerExtension container) { InitializeComponent(); this._container = container; IPerson person = container.Resolve<IPerson>("man");//man }
2.通過實例來注冊:
通過實例的方式注冊的對象屬於單例
通過實例注冊,將實例放入容器,可以按照名稱來注冊這個實例,也可以按照類型來注冊這個實例
通過名稱來注冊實例:
protected override void RegisterTypes(IContainerRegistry containerRegistry) { IPerson person = new Man(); containerRegistry.RegisterInstance<IPerson>(person,"man"); }
通過類型注冊來注冊實例:
protected override void RegisterTypes(IContainerRegistry containerRegistry) { IPerson person = new Man(); containerRegistry.RegisterInstance<IPerson>(person); }
3.自動構造注入:
當我們在RegisterTypes函數中注冊了IPerson類型時,我們Resolve其他任意一個具體類時,類的構造函數的參數類型,容器都會嘗試自動注入解決,自動注入遵循以下規律:
- 存在容器中的類型,自動注入到該參數
- 該參數類型不存在容器中,嘗試new一個該類型,也嘗試解決該類的構造函數的所有參數類型
例子:
注冊IPerson類型:
protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterInstance<IPerson,Man>(person); }
當我們嘗試得到一個Animal對象時,由於Animal的構造函數中有IPerson類型,IPerosn類型已經在容器中注冊了,所以容器會自動將之前注冊的IPerosn類型注入到構造函數中:
public MainWindow(IContainerExtension container) { InitializeComponent(); this._container = container; var animal = container.Resolve<Animal>(); string sex=animal.BelongPerson.Sex;//man,IPerson通過Animal的構造函數自動注入進來 }
3.通過配置文件app.config注入類型到容器
通過配置文件注冊,需要引用Unity.Configuration
具體通過配置文件注入,請參考Unit.Configuration里的測試用例:
https://github.com/unitycontainer/configuration/tree/master/tests/ConfigFiles
我們在app.config中配置如下:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/> </configSections> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> </startup> <unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <container> <register type="SimplePrismAppTest.Model.IPerson,SimplePrismAppTest" mapTo="SimplePrismAppTest.Model.Man,SimplePrismAppTest" name="A"></register> <register type="SimplePrismAppTest.Model.IPerson,SimplePrismAppTest" mapTo="SimplePrismAppTest.Model.Woman,SimplePrismAppTest" name="B"></register> <register type="SimplePrismAppTest.Model.Animal,SimplePrismAppTest" mapTo="SimplePrismAppTest.Model.Animal,SimplePrismAppTest"> <constructor> <param name="owner" > <dependency name="A" /> </param> </constructor> </register> </container> </unity> </configuration>
在RegisterTypes的代碼如下:
protected override void RegisterTypes(IContainerRegistry containerRegistry) { UnityConfigurationSection section (UnityConfigurationSection)ConfigurationManager.GetSection("unity"); section.Configure(containerRegistry.GetContainer()); }
取值的時候:
public MainWindow(IContainerExtension container) { InitializeComponent(); this._container = container; IPerson person1 = container.Resolve<IPerson>("A");//man IPerson person2 = container.Resolve<IPerson>("B");//woman Animal animal = container.Resolve<Animal>(); bool result = animal.BelongPerson.Sex == person1.Sex;//true,animal的BelongPerson注入的是A }
4.通過其他配置文件注入類型到容器
當通過app.config注入類型到容器時,我們通過ConfigurationManager來獲取配置文件內容
當是其他配置文件的時候,我們通過如下方式去獲取:
假定我們在程序目錄下有一個otherConfig.config文件,獲取代碼如下:
protected override void RegisterTypes(IContainerRegistry containerRegistry) { string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "otherConfig.config"); //加載配置文件 Configuration config = ConfigurationManager.OpenMappedExeConfiguration(new ExeConfigurationFileMap() { ExeConfigFilename=configPath}, ConfigurationUserLevel.None); UnityConfigurationSection section=(UnityConfigurationSection)config.GetSection("unity"); section.Configure(containerRegistry.GetContainer()); }
三、Prism的事件分發注入及使用:
這次要講的是Prism中的IEventAggregator,它就是事件總線的實現方式,讓兩個不相干的模塊能夠通過發布訂閱的方式實現0耦合通信。
下面來看看兩個不相干的窗體之間的通信,要想使用事件,要定義消息的格式,我們這里消息是字符串,需要繼承一個泛型類Prism.Events.PubSubEvent<T>,建立一個MessageEvent類繼承自Prism.Events.PubSubEvent<string>
public class MessageEvent: Prism.Events.PubSubEvent<string> { }
我們建立兩個窗體,分別為Window1,Window2:
Window1:

這個Window1窗體加載及按鈕的點擊事件代碼如下:
public partial class Window1 : Window { private IEventAggregator _eventAggregator; public Window1(IEventAggregator eventAggregator) { InitializeComponent(); this._eventAggregator = eventAggregator; } private void btnSend_Click(object sender, RoutedEventArgs e) { string msg = Microsoft.VisualBasic.Interaction.InputBox("請輸入發送內容:"); if (string.IsNullOrEmpty(msg)) return; _eventAggregator.GetEvent<MessageEvent>().Publish(msg); } }
Window2:
Window2窗體放一個名稱為tb的TextBlock用於顯示消息,代碼如下:
public partial class Window2 : Window { public Window2(IEventAggregator eventAggregator) { InitializeComponent(); eventAggregator.GetEvent<MessageEvent>().Subscribe(x => { this.tb.Text += x + "\r\n"; }, ThreadOption.UIThread); } }
在MainWindow中,我們點擊一個按鈕,show出這兩個窗體:
public partial class MainWindow : Window { private IContainerExtension _container; public MainWindow(IContainerExtension container) { InitializeComponent(); this._container = container; } private void btnShow_Click(object sender, RoutedEventArgs e) { var w1 = _container.Resolve<Window1>(); var w2 = _container.Resolve<Window2>(); w1.Show(); w2.Show(); } }
最后運行結果如下:

