Prism 4 文档 ---第4章 模块化应用程序开发
模块化应用程序是指将一个应用程序拆分成一系列的可以组合的功能单元。一个客户端模块封装了应用程序的一部分,并且通常是一系列相关的关注点。它可以包含一个相关的组件的集合,就像用户界面,应用程序功能,和一些业务逻辑,以及一些应用程序基础模块,比如应用程序级的日至服务或者用户认证。模块之间是相互独立的但是可以通过松耦合的手段进行通信。模块化的应用程序是的开发、测试、部署和扩展变得更新容易。
Prism为模块化应用程序开发和运行时模块管理提供支持。使用Prism的模块化开发功能可以帮助你节省时间,因为你不需用去实现和测试你自己的模块框架。Prism提供了以下的模块化开发功能:
- 一个模块的目录注册命名模块,记录每个模块的位置;你可以通过以下几种方式创建模块目录
- 通过使用代码定义模块或者使用XAML定义模块
- WPF:在同一目录下自动发现所有模块,而不需要集中定义模块
- WPF:使用配置文件定义模块
- 为模块声明元数据属性来说明初始化模式及依赖关系
- 集成到依赖注入容器中来支持模块间的松耦合关系
- 模块加载:
- 管理依赖关系:包含复制模块和循环检测以确保模块间以正确的顺序被加载,并且只被加载和初始化一次。
- 按需加载和在应用程序启动时在后台以最小化的方式下载模块,其余的模块在后台被加载和初始化,或者只有当被需要时进行加载和初始化。
public class MyModule : IModule { public void Initialize() { // Do something here. } } |
ModuleCatalog提供了应用程序使用模块的所需的信息。其本质是一个ModuleInfo的类的集合,每个模块都是一个ModuleInfo,这个类记录了模块的名称、类型、位置和其他的一些属性。以下是上些通过ModuleInfo实例生成ModuleCategory的典型方法:
- 在代码中注册模块
- 在XAML中注册模块
- 在配置文件中注册模块(仅WPF)
- 通过本地目录发现模块(仅WPF)
- 如果是应用程序必须的模块,那么必须同应用程序一同下载并在应用程序启动时被加载。
- 如果模块包含了应用程序的常用功能,那么就可以在即将需要它的时候在后台下载并加载。
- 如果模块所包含的功能并非常用功能(或提供的功能不是其他模块所必须的)就可以按需要进行加载。
Prism提供了以下两个类负责应用程序启动过程:UnityBootstrapper和MefBootstrapper。这些类可以用于创建和配置模块的发现和加载的管理。你可以通过几行简单的代码重写配制方法来同XAML文件、配置文件、本地目录的形式加载模块。
- 将模块的视图添加到应用程序的导航结构中,这一般发生在使用视图发现或者视图注入的方式构建复杂的应用程序界面时
- 订阅应用程序级事件或者服务
- 向应用程序的依赖注入容器中注册共享服务。
- 松耦合事件。一个模块可以广播某些事情发生了,其他的模块可以订阅这些事件,以保证当事件发生时它们可以收到通知。松耦合事件是一种在两个模块间建立的轻量级的通信方法。因而,它们很容易被实现。然而,一个过度依赖事件的设计会变得很难维护,尤其为了实现一个任务而安排的许多事件。在那种情况下,最好考虑使用共享服务的方式。
- 共享服务。共享服务是一个可以通过通用接口访问的类,通常,共享服务在公共程序集中定义,并且提供了系统层面的服务,例如用户认证、日志或者配置。
- 共享资源。如果你不希望模块间直接通信,你可以通过共享资源来实现间接访问,例如数据库或者一些Web服务。
- 将模块中的类型同XAP进行关联
- 支持WPF和Silverlight中通过XAML和代码属性的方式注册模块
- 在WPF中通过配置文件和目录扫描的方式注册模块
- 模块加载时的状态跟踪
- 使用MEF时自定义模块元数据
- 确定你将会使用的框架。你可以创建自己的模块化框架、使用Prism、MEF或者其他的框架。
- 确定如何组织你的解决方案。通过定义每个模块的边界来实现模块化结构,包括每个模块都有那写程序集。你可以使用模块化开简化开发过程,以及控制应用程序将会如何部署或者它是否支持插件式开发的扩展架构。
- 确定如何划分你的模块。模块可以根据需求划分,例如,功能区,提供模块,开发团队和部署需求。
- 确定应用程序为所有模块提供的和兴服务。例如错误报告服务或者用户认证授权服务。
- 如果你选择使用Prism,确定使用何种方式向模块目录注册模块。在WPF中,你可以通过代码、XAML、配置文件或者本地目录扫描的方式注册模块。在Silverlight中,可以通过代码或者XAML的方式注册模块
- 确定模块的通信方式及依赖策略。模块之间需要相互通信,并且你需要处理模块间的依赖关系。
- 确定依赖注入容器。通常,模块化系统开发需要使用依赖注入,控制反转(IoC),或者服务定位器来保证模块间的松散耦合及动态的创建和加载模块。Prism支持使用Untiy、MEF和其他的依赖注入容器,并且提供了基于Unity和MEF的应用程序的类库支持。
- 最小化应用程序启动时间。考虑按需加载和后台下载模块来最小化应用程序启动时间。
- 确定部署需求。你需要考虑你将打算如何部署应用程序。这会影响到放入XAP中的程序集的数量。你可能也需要划分类似Prism类库的共享库以利用Silverlight的程序缓存。
- 模块包含特定的应用程序功能,就像Stock Trader Reference Implementation (Stock Trader RI)中的新闻模块
- 模块包含子系统或者一系列用户用例相关的功能。例如,采购,开发票,做总账
- 模块包含基础服务,比如日志服务,缓存,认证服务,Web服务
- 模块包含调用其他业务支撑系统(LOB)的服务,例如客户关系管理系统(Sieble CRM)和企业管理解决方案软件系统(SAP),此为还有一些其他内部系统
- 下载大小和共享依赖。每个XAP文件都会有少量的额外信息比如mainfest(文件列表)和.zip包内容。当然,如果模块之间存在一些通用依赖并且不能够从依赖模块中分离出来或者缓存类库的话,每个XAP文件将会包含这些依赖库,这样将会显著的增加下载文件的大小。
- 应用程序调用多个模块的时间顺序。如果统一时间多个模块被下载和使用,例如应用程序启动时显示视图,将它们打包在一个XAP文件中将会是的下载更快,同时也有助于保证它们在同一事件被应用程序使用。Prism的模块化功能可以保证所有依赖即使在不同的XAP文件中,也可以以正确的顺序被加载。就算是下载大小完全相同的情况下,下载一个文件的性能也比下载两个文件要好。
- 模块版本。如果不同的模块将会在不同的时间点开发并且肯能会单独的部署,你将会将他们分别放到各自的XAP文件中,那样是的模块的版本更加清楚,也有助于独立的升级。
- 将共享依赖分配到一个独立的基础模块中,并让其他模块都依赖它。
- 使用Sliverlight的Assembly Library Caching 将共享类型放到共享类库中去,这就会下载一次并且被Silverlight缓存而不是被Prism的下载模块缓存。
- 引导程序启动模块的的初始化过程,模块加载器加载并初始化OrdersModule
- 在OrdersModule的初始化过程中,向容器中注册OrdersRepository
- 模块加载器加载CustomersModule,模块的加载顺序可以在模块元数据的依赖关系中特殊设置
- CustomersModule的构造方法通过容器解析一个CustomerViewModel实例,CustomerViewModel含有一个通过函数注入或者属性注入的OrdersRepository(通常是基于它的接口)的依赖,基于OrdersModule的注册的类型,容器将依赖关系通过构造方法注入到视图模型中。连接的结果就是一个从CustomersViewModel到OrdersRepository类之间没有通过紧耦合连接的接口引用。
public class MyModule : IModule { public void Initialize() { // Initialize module } } |
protected override void ConfigureModuleCatalog() { Type moduleCType = typeof(ModuleC); ModuleCatalog.AddModule( new ModuleInfo() { ModuleName = moduleCType.Name, ModuleType = moduleCType.AssemblyQualifiedName, }); } |
文件。
注意:
Bootstrapper基类提供了CreateModuleCatlog方法来实ModuleCatalog对象的创建。默认情况下,这个方法将会创建一个ModuleCatalog实例,但是,这个方法可以被其他继承类重写,以实现创建不同的类型的模块目录。
XAML ModularityWithUnity.Silverlight\ModulesCatalog.xaml | Copy Code |
---|---|
<Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism"> <Modularity:ModuleInfoGroup Ref="ModuleB.xap" InitializationMode="WhenAvailable"> <Modularity:ModuleInfo ModuleName="ModuleB" ModuleType="ModuleB.ModuleB, ModuleB, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </Modularity:ModuleInfoGroup> <Modularity:ModuleInfoGroup InitializationMode="OnDemand"> <Modularity:ModuleInfo Ref="ModuleE.xap" ModuleName="ModuleE" ModuleType="ModuleE.ModuleE, ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> <Modularity:ModuleInfo Ref="ModuleF.xap" ModuleName="ModuleF" ModuleType="ModuleF.ModuleF, ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" > <Modularity:ModuleInfo.DependsOn> <sys:String>ModuleE</sys:String> </Modularity:ModuleInfo.DependsOn> </Modularity:ModuleInfo> </Modularity:ModuleInfoGroup> <!-- Module info without a group --> <Modularity:ModuleInfo Ref="ModuleD.xap" ModuleName="ModuleD" ModuleType="ModuleD.ModuleD, ModuleD, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </Modularity:ModuleCatalog> |
protected override IModuleCatalog CreateModuleCatalog() { return ModuleCatalog.CreateFromXaml(new Uri("/MyProject.Silverlight;component/ModulesCatalog.xaml", UriKind.Relative)); } |
以下代码说明了如果使用配置文件定义模块列表。如果你希望模块被自动加载,那么设置startupLoaded="true"。
XML ModularityWithUnity.Desktop\app.config | Copy Code |
---|---|
<modules> <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" /> <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"> <dependencies> <dependency moduleName="ModuleE"/> </dependencies> </module> </modules |
protected override IModuleCatalog CreateModuleCatalog() { return new ConfigurationModuleCatalog(); } |
protected override IModuleCatalog CreateModuleCatalog() { return new DirectoryModuleCatalog() {ModulePath = @".\Modules"}; } |
- 向应用程序注册模块视图。如果你的模块是使用视图发现和视图注入方式的用户界面的一部分,你的模块将需要为其视图或视图模型关联一个合适的区域名称。这样就可以将视图动态的展示到应用程序中的菜单、工具条、或者其他的可是化的区域中。
- 订阅应用程序级事件或者服务。通常,应用程序会暴露模块所关注的应用程序特定的服务 和/或事件。使用Initialze方法将模块的功能添加到这些应用程序级的事件和服务中。例如,应用程序在关闭时会引发事件,模块需要响应这些事件。也可能模块需要向应用程序级的服务提供一些数据。例如,如果你创建了一个MenuService(负责添加及删除菜单项),模块的Initialize方法将会是添加正确的菜单项的地方。
- 向依赖注入容器中注册类型。如果你正在使用一个例如Unity或MEF的依赖注入模式的话,模块就会注册应用程序或者其他模块会使用的类型,也可能会向容器解析一个模块所需要的类型。
C# (when using Unity) | |
---|---|
[Module(ModuleName = "ModuleA")] [ModuleDependency("ModuleD")] public class ModuleA: IModule { ... } |
XAML ModulesCatalog.xaml | |
---|---|
<Modularity:ModuleInfo Ref="ModuleF.xap" ModuleName="ModuleF" ModuleType="ModuleF.ModuleF, ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" > <Modularity:ModuleInfo.DependsOn> <sys:String>ModuleE</sys:String> </Modularity:ModuleInfo.DependsOn> </Modularity:ModuleInfo> |
XML App.config | |
---|---|
<modules> <module assemblyFile="Modules/ModuleD.dll" moduleType="ModuleD.ModuleD, ModuleD" moduleName="ModuleD"> <dependencies> <dependency moduleName="ModuleB"/> </dependencies> </module> |
C# Bootstrapper.cs | |
---|---|
protected override void ConfigureModuleCatalog() { Type moduleCType = typeof(ModuleC); this.ModuleCatalog.AddModule(new ModuleInfo() { ModuleName = moduleCType.Name, ModuleType = moduleCType.AssemblyQualifiedName, InitializationMode = InitializationMode.OnDemand }); } |
在XAML中定义模块目录中,指定InitializationMode.OnDemand,如下所示。
XAML ModulesCatalog.xaml | |
---|---|
... <Modularity:ModuleInfoGroup InitializationMode="OnDemand"> <Modularity:ModuleInfo Ref="ModuleE.xap" ModuleName="ModuleE" ModuleType="ModuleE.ModuleE, ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> ... |
在App.config中定义模块列表中,指定InitializationMode.OnDemand,如下所示。
XML App.config | |
---|---|
... <module assemblyFile="Modules/ModuleC.dll" moduleType="ModuleC.ModuleC, ModuleC" moduleName="ModuleC" startupLoaded="false"/> ... |
private void OnLoadModuleCClick(object sender, RoutedEventArgs e) { moduleManager.LoadModule("ModuleC"); } |
ModuleManager类为应用程序提供了跟踪模块下载进度的事件,它提供了已下载字节数和所需要下载的总字节数以及下载百分比,你可以利用这些数据向用户展示下载进度。
this.moduleManager.ModuleDownloadProgressChanged += this.ModuleManager_ModuleDownloadProgressChanged; |
void ModuleManager_ModuleDownloadProgressChanged(object sender, ModuleDownloadProgressChangedEventArgs e) { ... } |
this.moduleManager.LoadModuleCompleted += this.ModuleManager_LoadModuleCompleted; |
void ModuleManager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e) { ... } |
LoadModuleCompletedEventArgs类包含了一个IsErrorHandled事件,如果模块加载失败并且应用程序想要组织ModuleManager记录错误和抛出异常时,可以将这个属性设置为True
注意:
模块加载和初始化之后,这个模块的程序集就不能被卸载了。并且Prism类库将不会保留模块的实例引用,所以模块类的实例在初始化完成后将会被回收。
此节仅仅强调了当使用MEF作为依赖注入框架时的一些区别
当使用MEF时,MefModuleManager被MefBootstrapper调用。它扩展了ModuleManager并且实现了IPartImportsSatisfiedNotification接口以保证当新类别导入到MEF中时ModuleCatalog可以更新。
在使用MEF时,通过向模块类应用ModuleExport属性,以使MEF自动发现该类,如下代码所示。
[ModuleExport(typeof(ModuleB))] public class ModuleB : IModule { ... } |
ConfigureAggregateCatalog方法以注册程序集,代码如下所示。
protected override void ConfigureAggregateCatalog() { base.ConfigureAggregateCatalog(); //Module A is referenced in in the project and directly in code. this.AggregateCatalog.Catalogs.Add( new AssemblyCatalog(typeof(ModuleA).Assembly)); this.AggregateCatalog.Catalogs.Add( new AssemblyCatalog(typeof(ModuleC).Assembly)); } |
protected override void ConfigureAggregateCatalog() { base.ConfigureAggregateCatalog(); DirectoryCatalog catalog = new DirectoryCatalog("DirectoryModules"); this.AggregateCatalog.Catalogs.Add(catalog); } |
在WPF程序中使用MEF,使用ModuleExport属性,如下所示。
[ModuleExport(typeof(ModuleA), DependsOnModuleNames = new string[] { "ModuleD" })] public class ModuleA : IModule { ... } |
因为MEF允许你在运行时发现模块,所以在运行时也可能会发现组件的依赖关系。即使你使用了MEF的
如果你使用MEF,并且通过ModuleExport属性来指定模块和模块间的依赖关系,你就可以使用InitializationMode属性来指定某个模块是否按需加载,如下所示。
[ModuleExport(typeof(ModuleC), InitializationMode = InitializationMode.OnDemand)] public class ModuleC : IModule { } |
学习更多关于Prism模块化有关内容,请查看Modularity with MEF for WPF QuickStart或者Modularity with Unity for WPF QuickStart。有关QuickStarts更多信息,请参考Modularity QuickStarts for WPF.
关于Prism类库中扩展模块化功能的内容,请参考"Modules" in "Extending Prism."