本文章使用的Prism、Prism.Unity版本:7.2.0.1422
需要说明的是:老版本的Prism,构建WPF应用是新建一个类,继承自UnityBootstrapper。但是新版本的已经不建议这么做了,而是App类直接继承自PrismApplication
,具体可以查看新版本中对UnitBootstrapper的注释说明:
源码地址:https://github.com/PrismLibrary/Prism/blob/master/Source/Wpf/Prism.Unity.Wpf/Legacy/UnityBootstrapper.cs
.NET版本选择最高版4.7.2.
选择Prism.Unity进行安装,安装过程中,会弹出如下界面:
说明Prism.Unity直接或间接依赖了这么多的包,其中:
这三个包是必须要了解的
这个包是当我们需要通过配置文件来实现容器注入时需要用到的
<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
///
/// App.xaml 的交互逻辑
///
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
这里要引入Prism.Ioc
和Prism.Unity
两个命名空间
这样我们的第一个Prism引用程序就搭建好了
平时我们启动的App是继承自System.Windows
下的Application类
我们将PrismApplication转到定义发现是这样的一种继承关系:
PrismApplicationBase中定义了两个三个抽象方法和若干个虚方法:
///
/// Creates the container used by Prism.
///
/// The container
protected abstract IContainerExtension CreateContainerExtension();
///
/// Used to register types with the container that will be used by your application.
///
protected abstract void RegisterTypes(IContainerRegistry containerRegistry);
///
/// Creates the shell or main window of the application.
///
/// The shell of the application.
protected abstract Window CreateShell();
其中CreateContainerExtension
方法被PrismApplication实现了:
protected override IContainerExtension CreateContainerExtension()
{
return new UnityContainerExtension();
}
所以App继承PrismApplication后,必须实现另外两个抽象方法:RegisterTypes()
和CreateShell
()
在了解注入之前,我们首先了解一下几个接口及类的关系,他们是:
IContainerExtension
:注册对象、取对象的泛型接口由以上得出的结论是:
关系如下图所示:
代码方式的注入,我们只能通过构造函数去注入
上面在分析PrismApplication
的时候,PrismApplicationBase
类有重写OnStartup
方法。查看源码,我们发现OnStartup
方法里调用了RegisterRequiredTypes
这个方法,这个方法如下:
源码地址:https://github.com/PrismLibrary/Prism/blob/master/Source/Wpf/Prism.Wpf/PrismApplicationBase.cs
line 116行
///
/// Registers all types that are required by Prism to function with the container.
///
///
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
}
我们关注常用的几个类型:
这样,我们通过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;
}
}
首先我们定义三个类和一个接口
接口:
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
{
///
/// 动物的主人
///
public IPerson BelongPerson;
public Animal(IPerson owner)
{
this.BelongPerson = owner;
}
}
在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
注册的是非单例的对象,也就是每次Resolve
的时候,容器每次帮我们创建了一个新对象。如果需要容器每次给我们的是同一个对象,就需要用RegisterSingleton
:
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
}
通过实例的方式注册的对象属于单例
通过实例注册,将实例放入容器,可以按照名称来注册这个实例,也可以按照类型来注册这个实例
通过名称来注册实例:
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);
}
当我们在RegisterTypes
函数中注册了IPerson类型时,我们Resolve其他任意一个具体类时,类的构造函数的参数类型,容器都会尝试自动注入解决,自动注入遵循以下规律:
例子:
注册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的构造函数自动注入进来
}
通过配置文件注册,需要引用Unity.Configuration
具体通过配置文件注入,请参考Unit.Configuration里的测试用例:
https://github.com/unitycontainer/configuration/tree/master/tests/ConfigFiles
我们在app.config中配置如下:
<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
}
当通过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中的IEventAggregator,它就是事件总线的实现方式,让两个不相干的模块能够通过发布订阅的方式实现0耦合通信。
下面来看看两个不相干的窗体之间的通信,要想使用事件,要定义消息的格式,我们这里消息是字符串,需要继承一个泛型类Prism.Events.PubSubEvent
,建立一个MessageEvent类继承自Prism.Events.PubSubEvent
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();
}
}
最后运行结果如下: