Prism初研究之使用Prism 5.0開發模塊化應用


Prism初研究之使用Prism 5.0開發模塊化應用

模塊化應用:分割成一組松耦合的功能單元(模塊),然后將這些模塊集成為一個大型的應用。一個模塊實現一組應用的全局功能和一組相關的概念,它可以包含一組相關的組件,UI和業務邏輯,或者是應用的基礎類型,比如應用級別的日志服務和權限認證。模塊之間通過松耦合的方式進行通信。松耦合的模塊化應用,非常便於開發、測試、配置和維護。
下圖表示一個模塊化應用的設計。
模塊合成

模塊化應用的優點:

你可能已經建立了一個架構良好的應用程序,使用了程序集、接口和類,並且遵守了良好的面向對象設計原則。即使如此,你還是需要小心翼翼,你的程序設計可能“巨大的”(應用中的功能以一種緊耦合的方式實現)。這樣的應用開發、測試、擴展和維護都很困難。
模塊化的應用能夠幫助你識別應用的功能領域,並且允許你對功能進行獨立的開發和測試。這種方式使開發和測試都很簡單,並且使應用更靈活,更容易擴展。模塊化應用的優點是使整個應用程序的架構更靈活,更容易維護。它將一個應用分成了可以管理的小塊,每一小塊都實現指定的功能,相互之間以松耦合的方式進行通信。

Prism支持模塊化應用開發

Prism支持模塊化的應用開發,並且能夠在運行時管理模塊。使用Prism可以節約開發時間,因為開發者無需實現自己的模塊測試框架。Prism支持的模塊開發特性:

  • 一個模塊分類用來注冊模塊和模塊的位置。創建模塊分類:
    • 編碼定義模塊,或者在XAML中定義模塊;
    • 在文件夾中發現模塊。因此可以裝載模塊而無需明確地指定模塊分類;
    • 在配置文件中定義模塊。
  • 為模塊聲明元特性來支持初始化和依賴性;
  • 使用DI容器來實現模塊間的松耦合;
  • 模塊加載:
    • 依賴管理,包括多次和循環檢查來確保模塊加載的順序和僅僅加載和初始化一次;
    • 按需后台下載模塊來最小化應用啟動時間,剩余的模塊可以在后台加載和初始化,或者延遲到需要時再加載和初始化。

核心概念

IModule:

一個模塊是一組功能和資源的邏輯組合。它可以是一個或多個程序集。每一個模塊都有一個核心類負責模塊的初始化和功能集成。這個類實現IModule接口。
IModule接口只有個Initialize方法,用來實現初始化邏輯和集成模塊的功能到應用。因此,IModule實現類可以注冊UI視圖,為應用提供擴展服務,甚至擴展應用程序的功能。示例代碼如下:

 
 
 
         
  1. public class MyModule : IModule
  2. {
  3. public void Initialize()
  4. {
  5. // Do something.
  6. }
  7. }

注意:Stock Trader RI使用基於特性的方式來注冊視圖、服務和類型,而不是使用IModule接口的初始化方法。

模塊的生命周期

模塊的加載過程:

  1. 注冊/發現模塊。模塊運行時的加載在Module Catalog中定義。Catalog包含需要加載的模塊、模塊位置以及模塊加載的順序等信息。
  2. 加載模塊。包含模塊的程序集被加載到內存。該階段可能需要模塊從遠程或者本地文件夾恢復。
  3. 初始化模塊。創建模塊類的實例,並且通過IModule接口調用Initialize方法。
    模塊加載過程:
    模塊加載過程

Module Catalog

ModuleCatalog維持模塊的信息。本質上,Catalog是一組ModuleInfo類。每一個模塊都被ModuleInfo類記錄,包括名字、類型、位置以及模塊的其它特性。有一些典型的方式來使用ModuleInfo實例設置ModuleCatalog:

  • 使用代碼注冊模塊
  • 使用XAML注冊模塊
  • 使用配置文件注冊模塊
  • 從本地硬盤文件夾發現模塊
    選擇注冊還是發現機制需要考慮應用的需求。使用配置文件或者XAML文件不需要模塊的引用。使用文件夾發現則不需要再文件中指定模塊。

控制Module加載

prism能夠提供兩種加載方式:盡可能快(when available)和需要時(on-demand)。考慮以下問題:

  • 應用需要的模塊必須在應用運行的時候加載和初始化。
  • 經常使用的應用模塊應該在盡快在后台進行加載和初始化。
  • 很少使用的模塊(或者其它模塊可選擇性依賴的模塊)應該在需要時進行加載和初始化。

考慮如何分割應用模塊,通用的情景,應用啟動時間以及下載的數量和大小來決定如何配置模塊的下載和初始化。

在應用程序中集成模塊

Prism提供兩個bootstrapper類:UnityBootstrapper和MefBootstrapper。這些類用來創建和配置模塊管理器來發現和加載模塊。可以通過重新Configuration方法來注冊指定的模塊(XAML文件、配置文件、目錄位置)
使用 模塊的Initialize方法來進行集成。具體的方法與應用程序的結構和模塊的內容有關。下面是集成模塊到應用通用的步驟:

  • 在應用的導航結構中添加模塊視圖。這是使用視圖發現和注入來構建復合UI應用的通用方法。
  • 訂閱應用程序級的事件和服務。
  • 使用應用程序的DI容器來注冊共享的服務。

模塊間的通信

盡管模塊之間是低耦合的,但是模塊間互相通信很常見。目前有一些松耦合的通信模式,它們有各自的優勢。通常,使用這些模式的組合來形成最終的解決方案。下面是這些模式:

  • 松耦合的事件。一個模塊可以廣播一個事件發生了。其它的模塊可以訂閱這些事件,一旦事件發生,它們就會得到通知。松耦合的事件在處理兩個模塊間通信時是一種輕量級的方式(容易實現)。然后一個設計如果依賴了太多的事件將會變的難以維護,尤其是許多事件需要精心安排來完成單一的工作。這種情況下最好考慮使用共享服務。
  • 共享服務。一個共享服務是一個通過通用接口訪問的類。一般共享服務在共享的程序集中,並且提供系統級的服務,比如權限認證,日志,或者配置。

* 共享資源。如果不想模塊間進行直接的通信,可以選擇通過共享的資源進行間接通信,比如數據庫或者網絡服務。

DI和模塊化應用

使用像Unity和MEF這樣的容器可以非常簡單的實現控制反轉(IoC)和依賴注入(DI)(一種使用松耦合的方式組合組件的設計模式)。DI允許組件無需硬編碼其它組件的引用就可以獲得其它組件的引用,因此代碼更容易復用並且靈活性更高。DI在構建松耦合的、模塊化的應用時非常有用。Prism框架被設計成對DI容器是透明的。DI容器的選擇完全由開發者決定,並且很大程度上依賴於應用的需求和預設。微軟提供了兩種DI框架來選擇——Unity和MEF。
Unity的模式和實踐提供了一個富有特色的DI容器。它支持基於屬性和基於構造器的注入和政策注入,允許開發者透明地在組件間注入行為和政策;當然,它也提供其它DI容器的典型特征。
MEF(.NET Framework 4.5的一部分)為構建可擴展的.NET應用程序提供支持,通過支持基於依賴注入的組件組裝和提供模塊化應用開發的其它特性。MEF運行應用程序在運行時發現組件,然后以松耦合的方式集成這些組件。MEF是一個偉大的可擴展的復合框架。它包括程序集和類型發現,類型依賴解析,依賴注入,和一些不錯的組件下載能力。Prism利用了MEF特性的一些優勢:

  • 通過XAML和代碼特性來進行模塊注冊。
  • 通過配置文件和文件夾掃描來注冊模塊。
  • 模塊加載時的狀態跟蹤。
  • 使用MEF時可以為模塊自定義聲明元數據。

Unity和MEF都可以和Prism無縫地工作。

關鍵決策

第一個決策:是否需要一個模塊化的解決方案。前面章節描述了模塊化應用的許多優點,但是需要花費一定的時間來獲得這些好處。如果覺得選擇模塊化的解決方案,還有很多事需要考慮:

  • 決定要使用的框架。可以使用Prism、MEF或者其它框架來實現自己的模塊化框架。
  • 決定如何組織解決方案。一個模塊化的架構需要定義模塊之間的邊界,包括每個模塊由那些程序集構成。開發者不僅要使用模塊來減輕開發,還需要控制應用程序未來如何部署,或者是否需要支持插件的或者可擴展的架構。
  • 決定如何分割模塊。根據需求,模塊能夠被分割的不盡相同。比如,通過功能域,提供商的模塊,開發小組和部署需求。
  • 決定應用需要提供給所有模塊的核心服務。一個核心服務的例子:錯誤報告服務、認證服務和授權服務。
  • 如果使用Prism,決定注冊模塊分類的方式。WPF,可以通過編碼、XAML、配置文件或者硬盤文件夾發現來注冊模塊。決定模塊的通信方式和依賴策略。模塊間需要互相通信,因此需要處理模塊間的依賴關系。
  • 決定DI容器。典型的模塊化系統需要依賴注入,控制反轉和服務定位器來實現松耦合和動態加載和創建模塊。Prism提供Unity,MEF,其它DI容器,以及提供Unity和基於MEF應用等多種選擇。
  • 最小化啟動時間。仔細考慮按需加載和后台下載模塊來最小化應用程序的啟動時間。
  • 決定部署需求。需要提前想好 如何部署應用程序。

將應用分割成模塊

當采用模塊化開發方式開發應用時,開發者需要將應用分解成可以單獨開發、測試和部署的離散模塊。每一個模塊封裝應用功能的一個子集。第一個需要決定的問題是如何將應用的功能分解成為離散的模塊。
一個模塊應該封裝了一組相關的概念並擁有一組明顯的責任。一個模塊可以表示應用的一個垂直切面,或者是一個水平的服務層。大型的應用程序一般包含兩種類型的模塊。
垂直切面的模塊組織
垂直切面的模塊組織
水平分層的模塊組織
水平分層的模塊組織
大型的應用可能有垂直和水平兩種模塊組織方式。比如:

  • 包含特定應用程序特性的模塊,比如Stock Trader Reference Implementation(Stock Trader RI)中的新聞模塊
  • 包含一個特殊的子系統,或者一組相關用例功能的模塊,比如采購,開發票,計總賬。
  • 包含基礎服務的模塊,比如日志,緩存,認證服務,網絡服務。
  • 包含調用業務線系統的模塊,比如Sibel CRM系統和SAP,還有其它內部系統。

一個模塊應該保持對其它模塊的最小依賴。當一個模塊依賴另一個模塊,應該通過使用定義在共享類庫中的接口,或者使用EventAggregator來保持松散的耦合關系。
模塊化開發的目的就是為了讓應用在面臨功能新增、移除、變更時,依然保持靈活性、可維護性和穩定性。最佳實踐是,將應用分解成盡可能獨立、並且接口定義良好的模塊。

決定模塊在整個工程中的比例

有許多創建和打包模塊的方法。推薦的通行方法是為每一個模塊創建一個單獨的程序集。這有助於促進封裝,並幫助保持邏輯上的划分。還有助於決定模塊的邊界和方便模塊的配置。當然,在一個程序集中包含多個模塊並沒有什么問題,在某些情況下還可以減少解決方案中項目工程的數量。在大型應用中有10到50個模塊並不罕見。為每個模塊都建立一個單獨的工程,會給整個解決方案帶來一些復雜性並且會影響Visual Studio的性能。有時候,如果要嚴格遵守一個模塊一個程序集(VS工程),那么將模塊或者一組模塊放置在獨立的解決方案中來進行管理可能更好一些。

使用依賴注入來保持松耦合

模塊可能依賴於主程序或者其它模塊提供的組件和服務。Prism支持模塊間依賴注冊的功能來保證模塊的加載和初始化的順序正確。Prism還支持模塊加載時進行初始化。在模塊初始化過程中,模塊會檢索它需要的組件和服務的引用,並且/或者注冊一些組件和服務來為其它模塊提供服務。
一個模塊可以使用獨立的機制來獲得外部接口的實例而不是直接實例化一個具體的類型,比如使用DI容器或者工廠服務。像Unity和MEF等DI容器允許通過依賴注入來在需要時自動獲得一個接口和類型的實例。
下圖顯示了模塊加載時獲取/注冊組件和服務引用的典型順序:
依賴注入
在這個例子中,OrdersModule程序集定義了一個OrdersRepository類(和其它視圖和類一起實現訂單功能)。CustomerModule程序集定義了CustomerViewModel類(依賴OrdersRepository類,通過暴露一個接口)。應用的啟動步驟如下:

  1. bootstrapper開始模塊初始化過程,Module Loader加載並初始化OrdersModule模塊。
  2. OrdersModule模塊的初始化過程中,將OrdersRepository注冊到DI容器中。
  3. Module Loader加載CustomersModule模塊。模塊加載的順序可以通過模塊元數據
    中的依賴性來指定。
  4. CustomersModule通過DI容器解析構造了一個CustomerViewModel實例。CustomerViewModel依賴OrdersRepository(通過構造函數或者屬性注入依賴)。DI容器根據OrdersModule中注冊的類型,通過視圖模型的構造函數來注入依賴的。類圖上的表現就是,CustomerViewModel擁有一個OrderRepository的接口引用。

    注意:接口OrderRepository(IOrderRepository)可以單獨放在一個“共享服務”程序集或者“訂單服務”程序集中(僅僅包含服務的接口類型)。通過這種方式,處理CustomersModule和OrdersModule的依賴關系變得很簡單。

注意:這兩個模塊都擁有DI容器的隱式依賴,這種依賴在模塊加載其中通過構造函數注入。

核心情景

本節介紹編寫模塊化應用時將會面對的常見情景。這些情景包括:定義模塊,注冊和發現模塊,加載模塊,初始化模塊,指定模塊依賴,按需加載模塊,在后台遠程下載模塊,檢索已經加載的模塊。可以通過硬編碼、XAML、配置文件、或者搜索本地目錄來注冊和發現模塊。

定義模塊

 
 
 
         
  1. public class MyModule : IModule
  2. {
  3. public void Initialize()
  4. {
  5. //初始化模塊
  6. }
  7. }

按照需求來實現Initialize方法。模塊的類型,初始化的模式,依賴的其它模塊是在module catalog中定義的。對於catalog中的每一個模塊,模塊加載器都創建一個模塊實例,然后調用Initialize方法。模塊加載的順序按照modul catalog中指定的順序。實時的初始化順序,則由模塊下載完成、可以使用、滿足依賴條件來決定。
兩種方式定義模塊的Module Catalog:為模塊本身聲明特性,或者在module catalog文件中聲明。

注冊和發現模塊

應用程序能夠加載的模塊在module catalog中定義。Prism的Module Loader使用module catalog來決定哪些模塊,何時,以何種順序加載到應用中。
module catalog是一個實現了IModuleCatalog接口的類。這個類由bootstrapper來在應用初始化過程中創建。Prism提供了一些module catalog的不同實現。可以通過調研AddModule方法從其它數據源中遷移module catalog,也可以自定義的創建一個module catalog。

編碼注冊模塊

最基本的module catalog由ModuleCatalog提供。可以使用module catalog來編程指定模塊類型。也可以指定模塊名稱和初始化模式。調用ModuleCatalog類的AddModule方法來直接注冊模塊(在Bootstrapper類中)。示例如下:

 
 
 
         
  1. protected override void ConfigureModuleCatalog()
  2. {
  3. Type moduleCType = typeof(ModuleC);
  4. ModuleCatalog.AddModule(new ModuleInfo()
  5. {
  6. ModuleName = moduleCType.Name.
  7. ModuleType = moduleCType.AssemblyQualifiedName,
  8. });
  9. }

先前的例子中Shell直接引用了模塊類,所以模塊的類型已經被定義並且直接AddModule。這個例子中則需要使用typeof(Module)。

注意:如果應用程序擁有模塊類型的直接引用,就可以像上例增加模塊類型;否則,需要提供類型的完全限定名來定位程序集。

Stock Trader RI中有其它定義module catalog的示例(StockTraderRIBootstrapper.cs)。

注意:Bootstrapper基類提供CreateModuleCatalog方法來輔助ModuleCatalog的創建。默認情況下,該方法返回一個ModuleCatalog的實例,但是這個方法可以被派生類重載來創建不同類型的module catalog。

在XAML中注冊模塊

可以直接在XAML文件中聲明一個module catalog。這個XAML指定module catalog類創建的種類和加入的模塊類型。通常“.xaml文件”作為Shell工程中的資源。這個module catalog通過bootstrapper調用CreateFromXaml方法來創建。從技術角度來看,這和在代碼中定義ModuleCatalog非常相似,因為XAML文件中僅僅定義了需要實例化對象的層次結構。示例如下:

 
 
 
         
  1. <!-- ModulesCatalog.xaml -->
  2. <Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:sys="clr-namespace:System;assembly=mscorlib"
  5. xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism">
  6. <Modularity:ModuleInfoGroup Ref="file://DirectoryModules/ModularityWithMef.Desktop.ModuleB.dll" InitializationMode="WhenAvailable">
  7. <Modularity:ModuleInfo ModuleName="ModuleB" ModuleType="ModularityWithMef.Desktop.ModuleB, ModularityWithMef.Desktop.ModuleB, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  8. </Modularity:ModuleInfoGroup>
  9. <Modularity:ModuleInfoGroup InitializationMode="OnDemand">
  10. <Modularity:ModuleInfo Ref="file://ModularityWithMef.Desktop.ModuleE.dll" ModuleName="ModuleE" ModuleType="ModularityWithMef.Desktop.ModuleE, ModularityWithMef.Desktop.ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  11. <Modularity:ModuleInfo Ref="file://ModularityWithMef.Desktop.ModuleF.dll" ModuleName="ModuleF" ModuleType="ModularityWithMef.Desktop.ModuleF, ModularityWithMef.Desktop.ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
  12. <Modularity:ModuleInfo.DependsOn>
  13. <sys:String>ModuleE</sys:String>
  14. </Modularity:ModuleInfo.DependsOn>
  15. </Modularity:ModuleInfo>
  16. </Modularity:ModuleInfoGroup>
  17. <!-- Module info without a group -->
  18. <Modularity:ModuleInfo Ref="file://DirectoryModules/ModularityWithMef.Desktop.ModuleD.dll" ModuleName="ModuleD" ModuleType="ModularityWithMef.Desktop.ModuleD, ModularityWithMef.Desktop.ModuleD, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  19. </Modularity:ModuleCatalog>

注意:ModuleInfoGroups提供了一個簡便的方法來初始化一組模塊,它們可能在一個程序集中,以相同的方式進行初始化,或者僅僅是擁有相同的依賴。
模塊之間的依賴性可以在相同的ModuleInfoGroup中與模塊一起定義,但是不能在不同的ModuleInfoGroup中定義。
將模塊放入module Group中是可選的,這個屬性是一個包含所有模塊的集合。注意模塊不放在Group中也可以注冊。

在Bootstrapper類中,需要為ModuleCatalog指定XAML文件,如下所示:

 
 
 
         
  1. protected override IModuleCatalog CreateModuleCatalog()
  2. {
  3. return ModuleCatalog.CreateFromXaml(new Url("/MyProject;component/ModulesCatalog.xaml", UrlKind.Relative));
  4. }

使用配置文件注冊模塊

在WPF中,在“App.config”文件中指定模塊信息成為可能。這種方式的優勢是配置文件不會被編譯到應用程序中。這使得在運行時可以增加和移除模塊,而無需重新編譯應用程序。
下面的代碼顯示通過配置文件指定一個module catalog的示例。如果需要模塊自動加載,設置startupLoaded=”true”。

 
 
 
         
  1. <!-- ModularityWithUnity.Desktop\app.config -->
  2. <?xml version="1.0" encoding="utf-8" ?>
  3. <configuration>
  4. <configSections>
  5. <section name="modules" type="Microsoft.Practices.Prism.Modularity.ModulesConfigurationSection,Microsoft.Practices.Prism"/>
  6. </configSections>
  7. <modules>
  8. <module assemblyFile="ModularityWithUnity.Desktop.ModuleE.dll" moduleType="ModularityWithUnity.Desktop.ModuleE, ModularityWithUnity.Desktop.ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleE" startupLoaded="false"/>
  9. <module assemblyFile="ModularityWithUnity.Desktop.ModuleF.dll" moduleType="ModularityWithUnity.Desktop.ModuleF, ModularityWithUnity.Desktop.ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleF" startupLoaded="false"/>
  10. <dependences>
  11. <dependency moduleName="ModuleE"/>
  12. </dependences>
  13. </module>
  14. </modules>
  15. </configuration>

注意:即使你的程序集在全局程序集緩存中或者和應用程序在同一個文件夾中,assemblyFile屬性依然需要顯示地設置。這個屬性用於將moduleType映射到正確的IModuleTypeLoader。

在應用的Bootstrapper類中,需要為ModuleCatalog指定配置文件。需要使用ConfigurationModuleCatalog類,代碼如下:

 
 
 
         
  1. protected override IModuleCatalog CreateModuleCatalog()
  2. {
  3. return new ConfigurationModuleCatalog();
  4. }

注意:你依然可以通過編碼向ConfigurationModuleCatalog增加模塊。可以使用這種方式來確保應用程序必須的模塊必須被定義。

從本地目錄中發現模塊

Prism的DirectionModuleCatalog類允許為WPF指定一個本地目錄作為module catalog。這個module catalog將掃描指定的文件夾,並且搜索定義在應用中的模塊程序集。使用這種方式需要為模塊類聲明特性來指定模塊名稱和需要的依賴。典型的代碼如下:

 
 
 
         
  1. protected override IModuleCatalog CreateModuleCatalog()
  2. {
  3. return new DirectoryModuleCatalog() {ModulePath = @".\Modules"};
  4. }

加載模塊

ModuleCatalog配置好后,模塊已經准備好被加載和初始化。模塊的加載意味着模塊的程序集從硬盤傳入內存中。ModuleManager負責模塊加載和初始化的過程。

初始化模塊

模塊加載之后,它已經完成了初始化。這意味着模塊的實例一被創建就調用了它的Initialize方法。初始化是模塊集成到應用的過程。考慮以下可能:

  • 向應用注冊模塊的視圖。如果一個模塊是UI組件(使用視圖發現或者視圖注入),那么這個模塊需要將它的視圖、視圖模型與適當的顯示區域(Region)聯系起來。這就允許應用程序在菜單欄、工具欄或者其它視圖區域動態地顯示視圖。
  • 訂閱應用級別的事件和服務。應用程序經常暴露模塊關心的服務和/或事件。使用過Initialize方法向模塊添加這些應用級別的事件和服務。
    比如,應用在關閉時可能觸發一個事件,並且模塊需要對這個事件作出反應。模塊也可能需要向一個應用級別的服務提供相同的數據,比如,如果你創建了一個菜單服務(負責增加/移除菜單項),模塊的Initialize中應該添加正確的菜單項。

    注意:模塊實例的聲明周期默認是短期的。在加載模塊的過程中調用完Initialize方法后,模塊實例的引用就會被釋放。如果你沒有維持一個模塊實例的強引用鏈,那么它將會被GC回收。
    如果你訂閱的事件在模塊中是弱引用,那么這種行為很難調試,因為模塊在GC運行時就會消失。

  • 使用DI容器注冊類型。如果使用Unity或MEF等DI容器,模塊需要向應用程序或其它模塊注冊類型。這樣在需要時可以向DI容器請求一個需要類型的實例。

指定模塊間的依賴

模塊經常依賴於其它的模塊。如果Module A依賴Module B,那么Module B必須在Module A前初始化。ModuleManager跟蹤依賴的蹤跡來保證模塊初始化的正確性。根據你定義module catalog的方式,你可以通過編碼、配置文件、XAML來定義模塊間依賴關系。

編碼指定依賴

在通過編碼注冊模塊和從本地目錄檢索模塊的WPF程序中,Prism提供了特性聲明。代碼如下:

 
 
 
         
  1. // 使用Unity
  2. [Module(ModuleName = "ModuleA")]
  3. [ModuleDependency("ModuleD")]
  4. public class ModuleA: IModule
  5. {
  6. ...
  7. }

在XAML中指定依賴

下面的XAML表示Module F依賴Module E:

 
 
 
         
  1. <!-- ModulesCatalog.xaml -->
  2. <Modularity:ModuleInfo Ref="file://ModularityWithMef.Desktop.ModuleE.dll" moduleName="ModuleE" moduleType="ModularityWithMef.Desktop.ModuleE, ModularityWithMef.Desktop.ModuleE, Version=1.0.0.0, Culture=neutral, PublickeyToken=null">
  3. <Modularity:ModuleInfo Ref="file://ModularityWithMef.Desktop.ModuleF.dll" moduleName="ModuleF" moduleType="ModularityWithMef.Desktop.ModuleF, ModularityWithMef.Desktop.ModuleF, Version=1.0.0.0, Culture=neutral, PublickeyToken=null">
  4. <Modularity:ModuleInfo.DependsOn>
  5. <sys:String>ModuleE</sys:String>
  6. </Modularity:ModuleInfo.Dependson>
  7. </Modularity:ModuleInfo>
  8. ...

在配置文件中指定依賴

通過App.xaml文件定義Module F依賴於Module E:

 
 
 
         
  1. <!-- App.config -->
  2. <modules>
  3. <module assemblyFile="ModularityWithUnity.Desktop.ModuleE.dll" moduleType="ModularityWithUnity.Desktop.ModuleE, ModularityWithUnity.Desktop.ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleE" startupLoaded="false"/>
  4. <module assemblyFile="ModularityWithUnity.Desktop.ModuleF.dll" moduleType="ModularityWithUnity.Desktop.ModuleF, ModularityWithUnity.Desktop.ModuleF, Version=1.0.0.0, Cultrue=neutral, PublicKeyToken=null" moduleName="ModuleF" startupLoaded="false">
  5. <dependencies>
  6. <dependency moduleName="ModuleE"/>
  7. </dependencies>
  8. </module>
  9. </modules>

按需加載模塊

首先需要在module catalog中將模塊的InitializationMode設置為OnDemand,然后需要在應用程序中編碼來請求加載模塊。

編碼指定On-Demand模式

使用屬性指定On-demand加載,代碼如下:

 
 
 
         
  1. // Bootstrapper.cs
  2. protected override void ConfigureModuleCatalog()
  3. {
  4. ...
  5. Type moduleCType = typeof(ModuleC);
  6. this.ModuleCatalog.AddModule(new ModuleInfo()
  7. {
  8. ModuleName = moduleCType.Name,
  9. ModuleType = moduleCType.AssemblyQualifiedName,
  10. InitializationMode = InitializationMode.OnDemand
  11. });
  12. ...
  13. }

XAML指定On-Demand模式

可以在XAML中定義module catalog時指定InitializationMode.OnDemand。

配置文件中指定On-Demand模式

可以在App.config文件中定義module catalog時指定InitializationMode.OnDemand。

請求加載一個On-Demand模式的模塊

需要一個IModuleManager服務的引用來加載模塊。這個服務在bootstrapper中注冊到DI容器中。

 
 
 
         
  1. private void OnLoadModuleCClick(object sender, RoutedEventArgs e)
  2. {
  3. moduleManager.LoadModule("ModuleC");
  4. }

檢索已經加載的模塊

ModuleManager服務提供了一個事件來跟蹤模塊的加載。可以使用IModuleManager接口通過DI容器獲得一個ModuleManager的引用。

 
 
 
         
  1. this.moduleManager.LoadModuleCompleted += this.ModuleManager_LoadModuleCompleted;
  2. void ModuleManager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e)
  3. {
  4. ...
  5. }

為了保持應用程序和模塊的松耦合,應該避免使用事件來集成模塊到應用程序。可以使用模塊的Initialize方法來集成。
LoadModuleCompleteEventArgs包含IsErrorHandled屬性。如果模塊加載失敗,應用程序又想阻止ModuleManager不記錄錯誤,不拋出異常,可以將該屬性設置為true。

注意:模塊加載和初始化之后,模塊的程序集就不能被卸載了。Prism框架不會維護模塊實例的引用,所有模塊類的實例有可能在初始化完成后被GC回收。

MEF中的模塊

本節介紹MEF的區別。

注意:當時有MEF時,MefBootstrapper使用MefModuleManager類。MefModuleManager擴展了ModuleModuleManager類並且實現了IPartImportsSatisfiedNotification接口來確保新類型引入到MEF時ModuleCatalog能夠更新。

使用代碼注冊模塊(MEF)

為模塊類聲明ModuleExport特性來使MEF自動發現類型。

 
 
 
         
  1. [ModuleExport(typeof(ModuleB), InitializationMode = InitializationMode.OnDemand)]
  2. public class ModuleB : IModule
  3. {
  4. ...
  5. }

也可以使用AssemblyCatalog類來發現和加載模塊。這個類被用來發現一個程序集中的所有外部模塊類。並且允許將許多catalog合並成一個邏輯上的catalog。默認情況下,MefBootstrapper類創建一個AggregateCatalog實例。你可以覆蓋ConfigureAggregateCatalog方法來注冊程序集:

 
 
 
         
  1. protected override void ConfigureAggregateCatalog()
  2. {
  3. base.ConfigureAggregateCatalog();
  4. this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(ModuleA).Assembly));
  5. this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(ModuleC).Assembly));
  6. ...
  7. }

Prism的MefModuleManager保持了MEF的AggregateCatalog和Prism的ModuleCatalog的同步,因此Prism可以發現ModuleCatalog增加的或者AggregateCatalog增加的模塊。

注意:MEF使用Lazy 來阻止內部和外部類型的實例化,直到需要時才實例化。

目錄中檢索模塊(MEF)

MEF提供DirectoryCatalog來檢查程序集目錄。需要覆寫ConfigureAggregateCatalog方法來注冊目錄。這種方法只在WPF中可行。
首先,需要在模塊類聲明ModuleExport特性,並且提供模塊的名稱。這樣才能允許MEF導入模塊,Prism保持ModuleCatalog更新。

 
 
 
         
  1. protected override void ConfigureAggregateCatalog()
  2. {
  3. base.ConfigureAggregateCatalog();
  4. ...
  5. DirectoryCatalog catalog = new DirectoryCatalog("DirectoryModules");
  6. this.AggregateCatalog.Catalogs.Add(catalog);
  7. }

在代碼中指定依賴(MEF)

使用MEF的WPF應用中,使用ModuleExport特性。

 
 
 
         
  1. [ModuleExport(typeof(ModuleA), DependsOnModules = new string[] {"ModuleD"})]
  2. public class ModuleA IModule
  3. {
  4. ...
  5. }

由於MEF允許在運行時發現模塊,所以也可能在運行時發現新的依賴關系。雖然可以同時使用MEF和ModuleCatalog,但是在從XAML文件和配置文件加載模塊時驗證ModuleCatalog的依賴鏈的正確性很重要。如果ModuleCatalog中有一個模塊,它被MEF加載,ModuleCatalog的依賴將會被使用,而DependsOnModuleNames特性將會被忽略。

指定On-Deman加載(MEF)

可以直接使用特性中的InitializationMode屬性:

 
 
 
         
  1. [ModuleExport(typeof(ModuleC), InitializationMode = InitializationMode.OnDemand)]
  2. public class ModuleC : IModule
  3. {
  4. ...
  5. }

擴展閱讀






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM