使用Prism库模块化开发WPF应用程序10分钟指南

模块化的应用程序是指一组松散耦合的功能单元组合成一个称为模块的应用程序,这些功能单元像外挂组件一样可以集成到更大的应用程序中。使用Prism库模块化开发WPF应用程序通常是将客户端各子模块封装成拥有独立功能的组件,通常的做法是将业务相关的功能独立出来做为子模块或子系统。

模块化应用程序是被划分为一组松散耦合的功能单元(称为模块)的应用程序,这些功能单元可以集成到更大的应用程序中。客户端模块封装了应用程序整体功能的一部分,通常代表一系列相关问题。它可以包括相关组件的集合,例如应用程序功能(包括用户界面和业务逻辑),或者应用程序基础结构的一部分,例如用于记录或验证用户的应用程序级服务。模块彼此独立,但可以以松散耦合的方式相互通信。使用模块化应用程序设计使您可以更轻松地开发,测试,部署和维护应用程序。

Prism库模块化开发WPF

例如,考虑一个个人银行业务应用程序。用户可以访问各种功能,例如在多个帐户之间进行转帐,支付账单以及从单个用户界面(UI)更新个人信息。但是,在后台,每个功能都封装在一个离散模块中。这些模块相互之间以及与后端系统(例如数据库服务器和Web服务)进行通信。应用程序服务将各个组件集成在每个不同的模块中,并处理与用户的通信。用户将看到一个看起来像单个应用程序的集成视图。

下图显示了具有多个模块的模块化应用程序的设计。

使用Prism库模块化开发WPF应用程序指南
使用Prism库模块化开发WPF应用程序指南

构建模块化应用程序的好处

您可能已经在使用程序集,接口和类来构建结构良好的应用程序,并采用良好的面向对象设计原则。即使这样,除非格外小心,否则您的应用程序设计可能仍然是“整体的”(其中所有功能都是在应用程序内以紧密耦合的方式实现的),这会使应用程序难以开发,测试,扩展和维护。 

另一方面,模块化应用程序方法可以帮助您识别应用程序的大规模功能区域,并允许您独立开发和测试该功能。这可以使开发和测试更加容易,但是也可以使您的应用程序更灵活,将来更容易扩展。模块化方法的好处在于,它可以使您的应用程序分解为可管理的部分,从而使您的整个应用程序体系结构更加灵活和可维护。每个部分都封装了特定的功能,并且每个部分都通过清晰但松散耦合的通信通道进行集成。

Prism对模块化应用程序开发的支持

Prism为模块应用程序开发和应用程序内的运行时模块管理提供支持。使用Prism的模块化开发功能可以节省您的时间,因为您无需实施和测试自己的模块化框架。Prism支持以下模块化应用程序开发功能:

  • 一个模块目录(module catalog),用于注册命名模块(modules)和每个模块的位置;您可以通过以下方式创建模块目录:
    • 通过使用代码或可扩展应用程序标记语言(XAML)定义模块
    • 通过发现目录中(一般为Modules目录)的模块,您可以加载所有模块,而无需在集中式目录中明确定义
    • 通过在配置文件中定义模块(通过配置文件加载)
    • 模块的声明性元数据属性以支持初始化模式和依赖项
  • 对于模块加载:
    • 依赖管理,包括重复和循环检测,以确保模块以正确的顺序加载,并且仅加载和初始化一次
    • 按需和后台下载模块,以最小化应用程序启动时间;其余模块可以在后台或需要时进行加载和初始化
  • 与依赖注入容器集成以支持模块之间的松散耦合

核心概念

本节介绍与Prism中的模块化相关的核心概念,包括 IModule 接口,模块加载过程,模块目录,模块之间的通信以及依赖项注入容器。

IModule:模块化应用程序的构建块

使用Prism库模块化开发WPF应用程序指南
使用Prism库模块化开发WPF应用程序指南

模块(Module)是功能和资源的逻辑集合,以可以分别开发,测试,部署和集成到应用程序中的方式打包。一个包可以是一个或多个程序集。每个模块都有一个中央类(central class,指模块初始化类),负责初始化模块并将其功能集成到应用程序中。该类实现 IModule 接口。

注意:实现该 IModule 接口的类的存在足以将包标识为模块。

IModule接口有两种方法,分别名为 OnInitializedRegisterTypes。两者都将对依赖项注入容器的引用作为参数。将模块加载到应用程序中时,RegisterTypes 将首先调用它,并且应将其用于注册该模块实现的任何服务或功能。接下来 OnInitialized调用该方法。在这里应该执行诸如视图注册或任何其他模块初始化代码之类的事情。

public class MyModule : IModule
{
    public void RegisterTypes(IContainerRegistry containerRegistry)
    {
        // register with the container that SomeService implements ISomeService
        // ISomeService is defined in the Infrastructure module, see app architecture diagram
        containerRegistry.Register<MyApplication.Infrastructure.ISomeService, SomeService>();
    }

    public void OnInitialized(IContainerProvider containerProvider)
    {
        // use the containerProvider to retrieve the instance of the Prism RegionManager
        // and register the view in this module with a specific region in the app
        var regionManager = containerProvider.Resolve<IRegionManager>();
        regionManager.RegisterViewWithRegion("MyModuleView", typeof(Views.ThisModuleView));
    }
}

模块生命周期(Module Lifecycle)

Prism中的模块加载过程包括以下顺序:

  • 通过在类内部实现 IModule 接口来创建注册模块。
  • 发现模块。在模块目录中定义了要在运行时为特定应用程序加载的模块。该目录包含有关要加载的模块的信息,例如它们的位置以及要加载的顺序。
  • 加载模块。包含模块的程序集将加载到内存中。
  • 初始化模块。然后初始化模块。这意味着创建模块类的实例,RegisterTypesOnInitialized通过IModule接口在其上调用和方法。

模块目录(Module Catalog)

模块目录实质上保存的是应用程序使用的模块信息,说白了就是保存编译后的 dll 和各种资源文件。每个模块均在一个ModuleInfo类中进行描述,该类记录模块的其他属性以及名称,类型和位置。有几种用ModuleInfo实例填充ModuleCatalog的典型方法:

  • 在代码中注册模块
  • 在XAML中注册模块
  • 在配置文件中注册模块
  • 在磁盘上的本地目录中发现模块

您应该使用的注册和发现机制取决于您的应用程序需要什么。使用配置文件或XAML文件使您的应用程序不需要引用模块。使用目录可以使应用程序发现模块而不必在文件中指定它们。

控制何时加载模块

Prism应用程序可以尽快初始化模块(称为“何时可用”),或者在应用程序需要它们时(称为“按需”)进行初始化。考虑以下有关加载模块的准则:

  • 应用程序运行所需的模块必须随应用程序一起加载并在应用程序运行时初始化。
  • 可以按需加载和初始化包含很少使用的功能的模块(或者是其他模块可选地依赖的支持模块)。

考虑如何对应用程序进行分区,常见的使用场景和应用程序启动时间,以确定如何配置应用程序进行初始化。

应用程序中集成模块

每个Prism.Dryloc.WpfPrism.Ninject.wpfPrism.Unity.Wpf程序集都提供一个基于Application的类,该类用作App类的基类。重写虚拟方法 CreateModuleCatalog 以创建所需类型的模块目录。

对于应用程序中的每个模块,实现 IModuleInfo接口以注册模块类型和服务。将模块集成到应用中时,以下是常见的事情:

  • 将模块的视图添加到应用程序的导航结构中。使用视图发现或视图注入构建复合UI应用程序时,这很常见。
  • 订阅应用程序级别的事件或服务。
  • 向应用程序的依赖项注入容器注册共享服务。

模块间通讯

即使模块之间的耦合度较低,但模块之间相互通信也是很常见的。有几种松散耦合的通信模式,每种都有各自的优势。通常,这些模式的组合用于创建结果解决方案。以下是其中一些模式:

  • 松散耦合的事件。模块可以广播某个事件已发生。其他模块可以订阅这些事件,以便在事件发生时得到通知。松耦合事件是在两个模块之间建立通信的一种轻量级方式。因此,它们很容易实现。但是,过于依赖事件的设计可能难以维护,特别是如果必须将多个事件组织在一起以完成一项任务时,尤其如此。在这种情况下,最好考虑共享服务。
  • 共享服务。共享服务是可以通过公共接口访问的类。通常,共享服务位于共享程序集中,并提供系统范围的服务,例如身份验证,日志记录或配置。
  • 共享资源。如果您不希望模块直接相互通信,也可以使它们通过共享资源(例如数据库或一组Web服务)间接通信。

依赖注入和模块化应用

诸如 UnityDrylocNInject 之类的容器使您可以轻松使用控制反转(IoC)和依赖注入,它们是功能强大的设计模式,有助于以松耦合的方式组成组件。 它允许组件获得对它们依赖的其他组件的引用,而不必对那些引用进行硬编码,从而促进了更好的代码重用和更高的灵活性。 在构建松耦合的模块化应用程序时,依赖注入非常有用。 Prism旨在与用于组成应用程序内组件的依赖项注入容器无关。

无论选择三个容器中的哪个容器,Prism都将使用容器来构造和初始化每个模块,以便它们保持松散耦合。

关键决定(Key Decisions)

您要做出的第一个决定是是否要开发模块化解决方案。如上一节所述,构建模块化应用程序有许多好处,但是要获得这些好处,您需要付出时间和精力。如果您决定开发模块化解决方案,则还需要考虑以下几点:

  • 确定您将使用的框架。您可以创建自己的模块化框架,使用Prism或其他框架。
  • 确定如何组织您的解决方案。通过定义每个模块的边界(包括哪些组件是每个模块的一部分)来实现模块化体系结构。您可以决定使用模块化来简化开发,并控制应用程序的部署方式或是否支持插件或可扩展架构。
  • 确定如何对模块进行分区。可以根据需求对模块进行不同的分区,例如,按功能区域,提供者模块,开发团队和部署要求。
  • 确定应用程序将提供给所有模块的核心服务。一个示例是核心服务可以是错误报告服务或身份验证和授权服务。
  • 如果您使用的是Prism,请确定要使用什么方法在模块目录中注册模块。对于WPF,可以在代码,XAML,配置文件中注册模块,也可以在磁盘上的本地目录中发现模块。
  • 确定您的模块通信和依赖策略。模块将需要彼此通信,并且您将需要处理模块之间的依赖关系。
  • 确定您的依赖项注入容器。通常,模块化系统需要依赖项注入,控制反转或服务定位器,以允许松散耦合以及动态加载和创建模块。Prism允许在使用Unity,Dryloc或NInject之间进行选择,并为基于Unity,Dryloc和NInject的应用程序提供库。
  • 最小化应用程序启动时间。考虑按需下载和后台下载模块,以最大程度地缩短应用程序启动时间。
  • 确定部署要求。您将需要考虑打算如何部署应用程序。

下一节将提供有关某些决策的详细信息。

将您的应用程序划分为模块

当您以模块化方式开发应用程序时,会将应用程序结构化为可以分别开发,测试和部署的单独的客户端模块。每个模块将封装应用程序整体功能的一部分。您必须做出的第一个设计决策是确定如何将应用程序的功能划分为离散的模块。

模块应封装一组相关的关注点,并具有不同的职责集。模块可以代表应用程序的垂直划分或水平划分。

垂直切片的应用程序
使用Prism库模块化开发WPF应用程序指南

应用程序按模块垂直划分

水平分层的应用程序
使用Prism库模块化开发WPF应用程序指南

水平划分的模块的应用程序

较大的应用程序可能具有由垂直和水平层组成的模块。模块的一些示例包括:

  • 包含特定应用程序功能的模块,例如提供新闻和/或公告的模块
  • 包含针对一组相关用例(例如购买,发票或总分类帐)的特定子系统或功能的模块
  • 包含基础结构服务(例如日志记录,缓存和授权服务或Web服务)的模块
  • 一个模块,除其他内部系统外,该模块包含调用业务线(LOB)系统的服务,例如Siebel CRM和SAP

一个模块对其他模块的依赖关系应最少。当一个模块依赖于另一个模块时,应使用共享库中定义的接口(而不是具体类型)或使用 EventAggregator 通过 EventAggregator 事件类型与其他模块进行通信来松散耦合

模块化的目的是对应用程序进行分区,以使其在添加和删除功能和技术时仍保持灵活,可维护和稳定。实现此目的的最佳方法是设计应用程序,以使模块尽可能独立,具有定义明确的接口,并尽可能隔离。

确定项目与模块的比例

有几种创建和打包模块的方法。推荐的最常见的方法是为每个模块创建单个装配件。这有助于使逻辑模块分离并促进适当的封装。它还使谈论装配作为模块边界以及如何部署模块的包装变得更加容易。但是,没有什么可以阻止单个程序集包含多个模块,在某些情况下,为了最大程度地减少解决方案中的项目数量,可能更可取。对于大型应用程序,具有10–50个模块并不罕见。将每个模块分成自己的项目会增加解决方案的复杂性,并可能降低Visual Studio的性能。

使用依赖注入进行松耦合

模块可以取决于主机应用程序或其他模块提供的组件和服务。Prism支持注册模块之间的依赖关系的功能,以便按正确的顺序加载和初始化它们。当模块加载到应用程序中时,Prism还支持模块的初始化。在模块初始化期间,模块可以检索对其所需的其他组件和服务的引用,和/或注册其包含的任何组件和服务,以使其可用于其他模块。

模块应该使用独立的机制来获取外部接口的实例,而不是例如通过使用依赖项注入容器或工厂服务来直接实例化具体类型。依赖注入容器(例如Unity,Dryloc或NInject)允许一种类型通过依赖注入自动获取其所需的接口和类型的实例。Prism与Unity,Dryloc和NInject集成在一起,以允许模块轻松使用依赖项注入。

下图显示了加载模块时需要获取或注册对组件和服务的引用的典型操作顺序。

依赖注入的示例
使用Prism库模块化开发WPF应用程序指南

在此示例中,OrdersModule程序集定义一个OrdersRepository类(以及其他实现订单功能的视图和类)。该CustomerModule组件限定了CustomersViewModel依赖于类OrdersRepository,通常是基于由服务暴露的接口上。应用程序的启动和引导过程包含以下步骤:

  1. App派生自的类PrismApplication开始模块初始化过程,并且模块加载器加载并初始化OrdersModule
  2. 在的初始化中OrdersModule,它将OrdersRepository向容器注册。
  3. 然后,模块加载器加载CustomersModule。可以通过模块元数据中的依赖关系来指定模块加载的顺序。
  4. CustomersModule构造的一个实例CustomerViewModel通过容器以解决该问题。该CustomerViewModel对的依赖性OrdersRepository(通常基于它的接口上),并指示它通过构造或财产注射。容器根据所注册的类型在视图模型的构造中注入该依赖性OrdersModule。最终结果是从CustomerViewModel到的接口引用,OrderRepository而这些类之间没有紧密的耦合。

注意:用于公开OrderRespositoryIOrderRepository)的接口可以驻留在单独的“共享服务”程序集或“订单服务”程序集中,其中仅包含公开这些服务所需的服务接口和类型。这样,CustomersModule和之间就没有硬性依赖OrdersModule

请注意,两个模块对依赖项注入容器都有隐式的依赖项。在模块构造器中的模块加载器中注入了此依赖性。

核心场景

本节介绍在应用程序中使用模块时遇到的常见场景。这些场景包括定义模块,注册和发现模块,加载模块,初始化模块,指定模块依赖性,按需加载模块,在后台下载远程模块以及检测模块何时已加载。您可以在代码中,XAML或应用程序配置文件中或通过扫描本地目录来注册和发现模块。

定义模块

模块是功能和资源的逻辑集合,以可以分别开发,测试,部署和集成到应用程序中的方式打包。每个模块都有一个中央类(central class),负责初始化模块并将其功能集成到应用程序中。该类实现了IModule接口,如此处所示。

public class MyModule : IModule
{
    public void RegisterTypes(IContainerRegistry containerRegistry)
    {
    }

    public void OnInitialized(IContainerProvider containerProvider)
    {
    }
}

实现 RegisterTypes使用依赖项注入容器来处理该模块实现的所有服务的注册。

如何 OnInitialized方法的实现将取决于应用程序的需求。在这里您可以注册视图并执行其他可能需要的其他模块级别的初始化。

注册和发现模块

使用Prism库模块化开发WPF应用程序指南
使用Prism库模块化开发WPF应用程序指南

应用程序可以加载的模块在模块目录中定义。Prism 模块加载器使用模块目录来确定哪些模块可用于加载到应用程序中,何时加载它们以及以什么顺序加载。

模块目录由实现 IModuleCatalog 接口的类表示。模块目录类是由 PrismApplication 基类在应用程序初始化期间创建的。Prism 提供了模块目录的不同实现,供您选择。您也可以通过调用该 AddModule方法或通过派生 ModuleCatalog 来创建具有自定义行为的模块目录来从另一个数据源填充模块目录。

默认情况下,派生自 PrismApplication 的App类在 CreateModuleCatalog 方法中创建 ModuleCatalog 。 覆盖此方法以使用不同类型的 ModuleCatalog 。

在代码中注册模块

ModuleCatalog 类提供最基本的模块目录(默认)。 您可以通过指定模块类类型,使用此模块目录以编程方式注册模块。 您还可以以编程方式指定模块名称和初始化模式。 要直接向ModuleCatalog类注册模块,请在应用程序的PrismApplication派生的App类中调用AddModule方法。 覆盖ConfigureModuleCatalog以添加模块。 以下代码显示了一个示例。

protected override void ConfigureModuleCatalog()
{
    Type moduleCType = typeof(ModuleC);
    ModuleCatalog.AddModule(new ModuleInfo()
    {
        ModuleName = moduleCType.Name,
        ModuleType = moduleCType.AssemblyQualifiedName,
    });
}

注意:如果您的应用程序直接引用了模块类型,则可以按上面所示按类型进行添加;否则,您需要提供完全限定的类型名称和程序集的位置。

要指定代码中的依赖关系,请使用Prism提供的声明性属性(标记特性)。

[Module(ModuleName = "ModuleA")]
[ModuleDependency("ModuleD")]
public class ModuleA : IModule
{
    ...
}

要在代码中指定按需加载,请将InitializationMode属性添加到ModuleInfo的新实例中。使用下面的代码:

Type moduleCType = typeof(ModuleC);
ModuleCatalog.AddModule(new ModuleInfo()
{
    ModuleName = moduleCType.Name,
    ModuleType = moduleCType.AssemblyQualifiedName,
    InitializationMode = InitializationMode.OnDemand,
});

使用XAML文件注册模块

您可以通过在XAML文件中指定模块目录来声明性地定义模块目录。 XAML文件指定要创建的模块目录类类型以及要添加到其中的模块。 通常,.xaml文件作为资源添加到您的Shell项目中。 应用程序通过调用CreateFromXaml方法来创建模块目录。 从技术角度来看,此方法与在代码中定义ModuleCatalog非常相似,因为XAML文件仅定义了要实例化的对象的层次结构。

以下代码示例显示了一个XAML文件,该文件指定了模块目录。

<--! ModulesCatalog.xaml -->
<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="file://DirectoryModules/ModularityWithMef.Desktop.ModuleB.dll" InitializationMode="WhenAvailable">
        <Modularity:ModuleInfo ModuleName="ModuleB" ModuleType="ModularityWithMef.Desktop.ModuleB, ModularityWithMef.Desktop.ModuleB, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </Modularity:ModuleInfoGroup>

    <Modularity:ModuleInfoGroup InitializationMode="OnDemand">
        <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" />
        <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">
            <Modularity:ModuleInfo.DependsOn>
                <sys:String>ModuleE</sys:String>
            </Modularity:ModuleInfo.DependsOn>
        </Modularity:ModuleInfo>
    </Modularity:ModuleInfoGroup>

    <!-- Module info without a group -->
    <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" />
</Modularity:ModuleCatalog>

注意: ModuleInfoGroups提供了一种方便的方法来对同一程序集中的模块,以相同的方式初始化或仅依赖于同一组中的模块的模块进行分组。模块之间的依赖关系可以在同一模块中定义ModuleInfoGroup;但是,您不能在different中定义模块之间的依赖关系ModuleInfoGroups。将模块放入模块组是可选的。为组设置的属性将应用于其所有包含的模块。请注意,模块也可以注册而不必在组中。

下面是有关从XAML文件创建目录的示例:

protected override IModuleCatalog CreateModuleCatalog()
{
    return ModuleCatalog.CreateFromXaml(new Uri("/MyProject;component/ModulesCatalog.xaml", UriKind.Relative));
}

要在XAML中指定依赖关系,请遵循以下示例:

<-- ModulesCatalog.xaml -->
<Modularity:ModuleInfo Ref="file://ModuleE.dll" moduleName="ModuleE" moduleType="ModuleE.Module, ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Modularity:ModuleInfo Ref="file://ModuleF.dll" moduleName="ModuleF" moduleType="ModuleF.Module, ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
    <Modularity:ModuleInfo.DependsOn>
        <sys:String>ModuleE</sys:String>
    </Modularity:ModuleInfo.DependsOn>
</Modularity:ModuleInfo>

要指定按需加载模块,请将startupLoaded属性添加到Modularity:ModuleInfo元素。

<Modularity:ModuleInfo Ref="file://ModuleE.dll" moduleName="ModuleE" moduleType="ModuleE.Module, ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" startupLoaded="false" />

使用配置文件注册模块

使用Prism库模块化开发WPF应用程序指南
使用Prism库模块化开发WPF应用程序指南

WPF中,可以在App.config文件中指定模块信息。这种方法的优点是不会将此文件编译到应用程序中。这使得在运行时添加或删除模块非常容易,而无需重新编译应用程序。

以下代码示例显示了一个指定模块目录的配置文件。

<!-- ModularityWithUnity.Desktop\app.config -->
<xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="modules" type="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf"/>
    </configSections>

    <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>
</configuration>

注意:即使您的程序集位于全局程序集缓存中或与应用程序位于同一文件夹中,该assemblyFile属性也是必需的。该属性用于将映射moduleType到正确IModuleTypeLoader使用的位置。

在应用程序的App类中,您需要指定配置文件是您的的源ModuleCatalog。为此,请重写该CreateModuleCatalog方法并返回ConfigurationModuleCatalog该类的实例。

protected override IModuleCatalog CreateModuleCatalog()
{
    return new ConfigurationModuleCatalog();
}

注意:您仍然可以将模块添加到ConfigurationModuleCatalogin代码中。例如,可以使用它来确保在目录中定义了应用程序绝对需要运行的模块。

要在app.config文件中指定依赖项:

<-- app.config -->
<modules>
    <module assemblyFile="ModuleE.dll" moduleType="ModuleE.Module, ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="moduleE" />
    <module assemblyFile="ModuleF.dll" moduleType="ModuleF.Module, ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="moduleF">
        <dependencies>
            <dependency moduleName="moduleE" />
        </dependencies>
    </module>
</modules>

要使用配置文件指定按需加载,请将元素的startupLoaded属性设置modulefalse

<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" />

在目录中发现模块

Prism 的 DirectoryModuleCatalog 类使您可以将本地目录指定为WPF中的模块目录。 该模块目录将扫描指定的文件夹,并搜索为您的应用程序定义模块的程序集。 要使用这种方法,您将需要在模块类上使用声明性属性来指定模块名称及其所具有的任何依赖关系。 以下代码示例显示了通过在目录中发现程序集来填充的模块目录。

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

要指定依赖关系,请使用与使用代码相同的方法。

要按需或在启动时处理加载,请Module按如下所示更新属性:

[Module(ModuleName = "ModuleA", OnDemand = true)]
[ModuleDependency("ModuleD")]
public class ModuleA : IModule
{
    ...
}

模块化需要注意的项目

请求按需加载模块

将模块指定为按需后,应用程序可以要求加载该模块。想要启动加载的代码需要获取 IModuleManager 对该App类中向容器注册的服务的引用。

可以通过以下代码来显式加载模块:

public class SomeViewModel : BindableBase
{
    private IModuleManager _moduleManager = null;

    public SomeViewModel(IModuleManager moduleManager)
    {
        // use dependency injection to get the module manager
        _moduleManager = moduleManager;
    }

    private void LoadSomeModule(string moduleName)
    {
        _moduleManager.LoadModule(moduleName);
    }
}

检测何时加载模块

ModuleManager服务为应用程序提供事件以跟踪模块加载或加载失败的时间。

public class SomeViewModel : BindableBase
{
    private IModuleManager _moduleManager = null;

    public SomeViewModel(IModuleManager moduleManager)
    {
        _moduleManager = moduleManager;
        _moduleManager.LoadModuleCompleted += _moduleManager_LoadModuleCompleted;
    }

    private void _moduleManager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e)
    {
        // ...
    }
}

为了保持应用程序和模块之间的松散耦合,应用程序应避免使用此事件将模块与应用程序集成在一起。相反,模块的 RegisterTypes OnInitialized 应该处理与应用程序的集成。

LoadModuleCompletedEventArgs包含IsErrorHandled属性。如果模块加载失败,并且应用程序希望防止ModuleManager日志记录错误并引发异常,则可以将此属性设置为true

注意:加载并初始化模块后,无法卸载模块部件。Prism库不会保存模块实例引用,因此初始化完成后可能会垃圾收集模块类实例。

本文翻译自:https://prismlibrary.com/docs/wpf/modules.html,有校正和注解。

User Review
0 (0 votes)
本站最新优惠

Namesilo优惠:新用户省 $1 域名注册-优惠码:45D%UYTcxYuCloZ 国外最便宜域名!点击了解更多

特别优惠:免费赠送 $100 Vultr主机-限时优惠!英文站必备海外服务器!点击了解更多

VPS优惠:搬瓦工优惠码:BWH3OGRI2BMW 最高省5.83%打开外面世界的一款主机点击了解更多

本文来自投稿作者:IT菜鸟,不代表江湖人士立场,如若转载,请注明出处:https://jhrs.com/2020/38045.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注

8 + 10 =