Prism+Prism.Unity的入門使用

Prism+Prism.Unity的使用

本文章使用的Prism、Prism.Unity版本:7.2.0.1422

一、使用Prism.Unity構建一個Prism應用

需要說明的是:老版本的Prism,構建WPF應用是新建一個類,繼承自UnityBootstrapper。但是新版本的已經不建議這麼做了,而是App類直接繼承自PrismApplication,具體可以查看新版本中對UnitBootstrapper的註釋說明:

源碼地址:https://github.com/PrismLibrary/Prism/blob/master/Source/Wpf/Prism.Unity.Wpf/Legacy/UnityBootstrapper.cs

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.IocPrism.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();
    }
}

最後運行結果如下:

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章