Prism+Prism.Unity的使用
本文章使用的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();
}
}
最後運行結果如下: