利用 Enterprise Library 自定义应用程序块加快开发速度[zt]
转自MSDN
http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/EnterpriseLibrary.mspx?mfr=true
利用 Enterprise Library 自定义应用程序块加快开发速度
Mark Seemann
本文将介绍以下内容:
• |
编写针对 Enterprise Library 的新应用程序块 |
• |
构建一个插件加载器应用程序块 |
• |
了解和实现工厂 |
• |
实现自定义应用程序块的配置 |
本文涉及如下技术:
• |
Enterprise Library、Visual Studio、C# |
可以从此处下载代码:
AppBlocks2006_07.exe(315KB)
本页内容
创建应用程序块 | |
对象的工厂 | |
创建工厂 | |
定义插件配置 | |
实现 PlugInProvider | |
设计时行为 | |
提供程序节点 | |
序列化和反序列化 XML | |
小结 |
Enterprise Library for Microsoft .NET Framework 2.0 是一个应用程序块库,而应用程序块则是一些模块化组件,设计用来帮助开发人员应对常见的开发挑战。它为构建健壮的可伸缩应用程序提供了一个可扩展框架。尽管 Enterprise Library 包含若干有用的应用程序块,但仍可以集成您自己的可重用组件。在本文中,我将演示如何构建一个与 Enterprise Library 集成的示例应用程序块。
Microsoft 的“模式和实施方案”团队是 Enterprise Library 的创建者,并且创造了“应用程序块”这个术语来描述超出 DLL 文件范畴的库。虽然应用程序块实际上是一个 DLL,但它具有更深一层的独特特征。
应用程序块是配置驱动式的,这意味着可使用配置来定义其行为。该配置可在标准的 .NET 配置文件中定义,也可以在运行时创建配置并将其注入到应用程序块中。某些配置将是典型的配置值,例如用于定义某些行为的条件的编号。但是在应用程序块方面,配置还可用来定义对基本块的扩展。
应用程序块还是模块化和可扩展的。可扩展性通常借助提供程序模型来实现;块将某些工作委托给一个提供程序,而由该提供程序实现块所使用的某个基类或接口。虽然块通常带有几个预先打包的提供程序(例如,允许您在 Windows® 事件日志中写入日志记录的提供程序,或允许您读写 SQL Server™ 数据的提供程序),但是也可通过在块的配置中定义提供程序类型来开发和替代其他提供程序。
何时您需要创建应用程序块而不是标准的库呢?如果您不需要配置库并且不想让其他人扩展它,则无需创建应用程序块。但是,如果这些功能可能会很有用,则应该考虑创建应用程序块。此外,由于 Enterprise Library 已经提供了大量可以利用的配置管理基础结构,您可以通过创建应用程序块而从中受益,创建独立的可配置库则无法享受这些好处。
应用程序块增加了库的复杂性,并且增加了对 Enterprise Library 中的其他块的依赖。但是反过来,您获得了一个公共的配置管理系统,并且能够使用 Enterprise Library 中的其他部分。例如,数据访问便是一种非常常见的需要。如果将应用程序块与 Enterprise Library 进行集成,便可以为块编写一个使用“数据访问应用程序块”的可选提供程序。在一切正确完成后,这会创建一种对“数据访问应用程序块”的可选依赖;如果不需要数据访问,则可以避免这种依赖。
创建应用程序块
在本文中,我将为您展示如何构建一个可用来管理应用程序插件的应用程序块。请注意,虽然我讲述的内容会涉及应用程序块开发的许多方面,但我在实现部分中将省略一些不适用于 Enterprise Library 的内容。因此,我在本文中创建的解决方案简单且很不安全。如果希望全面了解如何以安全和健壮的方式加载插件,请参见 Shawn Farkas 的 MSDN®Magazine 文章“您信任它吗?探寻使用 .NET Framework 2.0 安全承载不受信任的外接程序的技术”。由于我在这里要创建的 Plug-In Loader 应用程序块是可扩展的,因此可以开发一个利用 Shawn Farkas 文章中所介绍方法的新提供程序并将其添加到此应用程序块中。
可能上述免责声明有些跑题了,下面让我们开始。首先,我将定义应用程序块的最终目标。因为它的目的是加载插件,因此我认为一个合理的目标应该是使开发人员能够编写下面这样的代码:
PlugInManager mgr = PlugInFactory.GetManager(); IList plugIns = mgr.GetPlugIns(); // 在此处利用插件执行某些工作...
静态的 PlugInFactory 类新建一个可用来获取插件的 PlugInManager。在本文中,GetPlugIns 是 PlugInManager 的唯一成员。在更全面的实现中,可添加其他功能(例如,在运行时启用或禁用每个插件的能力)。PlugInManager 将实际工作委托给应用程序块的配置中指定的提供程序。然后,该提供程序根据其实现中的定义,加载并返回插件。
应用程序块包括运行时组件和可选的设计时组件。我将为您演示如何开发这两个组件。库的逻辑是在运行时组件中实现的。如果您想要利用应用程序块,则需要在项目中引用该文件。开发运行时组件就其本身来说涉及几个步骤。
设计时组件是可创建和包含的另一个库程序集。正如运行时组件与 Enterprise Library 运行时框架集成在一起,设计时组件也与 Enterprise Library 的设计时工具相集成。该设计时功能的主要存在目的便是:让开发人员使用应用程序块的过程更加容易。但是,它也可由系统管理员或其他用户使用,以提供一个图形化工具,对应用程序的配置进行编辑。
应用程序块是一个复杂的代码集合,包含大量的交互类。在您阅读本分步指南时,您可能奇怪为什么必须拥有那么多的不固定部分。其部分原因是:Enterprise Library 在如何实现应用程序块方面提供了极大的灵活性。在很多步骤里,Enterprise Library 使用抽象(例如接口和抽象类)来完成某些任务。在大多数此类方案中都包含了一个可扩展的默认实现,但是很少强迫您使用该默认实现。如果愿意,您可以创建自己的实现。
在 Enterprise Library 每次完成某个任务(例如从持久性存储中读取配置数据或在请求抽象类型时实例化具体类型)时,可以对默认行为进行扩展或从默认行为进行派生。对于 Enterprise Library 中的每个扩展点,通常都会涉及几个交互类。因为 Enterprise Library 包含大量的扩展点,因此也存在众多的交互类 —— 您很快就会自己发现这一点。
对象的工厂
Enterprise Library 使用工厂创建对象。它有自己的基于属性的方法,通过使用这种方法,您可利用类的工厂的类型来划分类的属性,然后通过从 Enterprise Library 提供的基类进行派生来实现该工厂。图 1 简要描述了典型的对象创建过程。正如您看到的,这个过程十分复杂,但必须记住的是:它完成的工作并不仅仅是创建了类型的任意实例。您请求一个抽象类型,然后根据配置数据获得该类型的正确实现,并且它已进行了适当的配置和检测。
此对象创建框架的可扩展性极好,您应将其作为一种对象创建机制用于自己的应用程序块,但是您很快就会发现需要创建大量的工厂类。图 2 简单描述了这些类之间的交互。在使用 Plug-In Loader 应用程序块时,静态的 PlugInFactory 类通常是入口点。该类仅仅是一个便利类 (Convenience Class),它使用 PlugInManagerFactory 创建 PlugInManager 的实例。大多数 Enterprise Library 应用程序块都提供这样的一个静态工厂类,将其作为通往应用程序块的入口点。在创建 Enterprise Library 应用程序块时,可以选择是否实现静态工厂类,但是我决定为 Plug-In Loader 包含一个这样的类,因为它在这里十分有用。
图 2 对象工厂类的交互
Enterprise Library 采用一种基于工厂的做法来创建可配置对象,虽然该框架十分灵活,但是它并不支持泛型,因为类型在编译时是未知的。出于这个原因,Plug-In Loader 的泛型类将它们的大部分工作委托给可由 Enterprise Library 创建和管理的非泛型类。PlugInManager 包装抽象的 PlugInProvider 类,PlugInManagerFactory 则包装 PlugInProviderFactory。如果您的应用程序块不需要支持泛型,则没有必要实现 PlugInManager 和 PlugInManagerFactory 这样的类。
PlugInProviderFactory 创建 PlugInProvider 的实例,它派生自 Enterprise Library 提供的一个类。通过使用在图 1 的步骤 1 中描述的 CustomFactory 属性,PlugInProvider 可标识 PlugInProviderCustomFactory,Enterprise Library 将 PlugInProviderCustomFactory 与配置类结合使用来创建 PlugInProvider 的实例。
这似乎比较复杂,但其中的大部分工作均由 Enterprise Library 负责执行。您需要实现相当多的类,但所有的类均十分简单。您可能会问自己为什么需要如此复杂。正如我前面提到的,您所做的并不仅仅是新建 PlugInProvider 的一个实例。对于初学者,您无法实例化 PlugInProvider,因为它是一个抽象类。当您请求 PlugInProvider 的实例的时候,应用程序块实际上必须根据配置数据来提供该抽象类的实现。Enterprise Library 使用此机制来识别要创建的内容和创建方式。
创建工厂
如果您看了我前面展示的预期目标代码,可以发现:我首先需要实现的类是 PlugInManager 和 PlugInFactory。这两个类将它们的大多数工作委托给其他类。PlugInManager 使用 PlugInProvider 执行实际工作,如图 3 所示。由于 Enterprise Library 对象创建机制不支持泛型,PlugInProvider 并不是一个泛型类,因此,为了返回 IList,PlugInProvider 返回的 IList 必须被转换为相应的类型安全集合。
PlugInProvider 本身是如图 4 所示的简单类。由于它是抽象的,因此它为 Plug-In Loader 提供了扩展点。如果希望扩展 Plug-In Loader,可从 PlugInProvider 派生一个新类并在该类中实现自定义逻辑。最值得注意的 PlugInProvider 功能是 CustomFactory 属性,它可指示 Enterprise Library 如何创建从 PlugInProvider 派生的类的新实例。此外还应注意抽象的 GetPlugIns 方法,该方法是继承者必须实现的方法。
PlugInProviderCustomFactory 派生自 Enterprise Library 提供的抽象类 AssemblerBased-CustomFactory。在从该类派生时,您必须做的唯一一件工作是:实现 GetConfiguration 方法。这里首次出现了一对新类:PlugInLoaderSettings 和 PlugInProviderData,如以下代码所示:
public class PlugInProviderCustomFactory : AssemblerBasedCustomFactory { protected override PlugInProviderData GetConfiguration( string name, IConfigurationSource configurationSource) { PlugInLoaderSettings settings = (PlugInLoaderSettings)configurationSource. GetSection(PlugInLoaderSettings.SectionName); return settings.PlugInProviders.Get(name); } }
这些类是配置类,我将在后面的部分中更详细地讨论它们。
现在,要注意的最重要事情是:GetConfiguration 方法返回相应的配置数据,使得 Enterprise Library 能够构造新的 PlugInProvider 对象,如图 1 所示。在该自定义工厂就位之后,我可以创建一个工厂类,随后使用该工厂类创建 PlugInProvider 实例,如以下代码所示:
public class PlugInProviderFactory : NameTypeFactoryBase { public PlugInProviderFactory(IConfigurationSource configSource) : base(configSource) { } }
虽然这又是另一个工厂类,但我需要做的全部工作就是从 NameTypeFactoryBase 类派生并提供一个公共的构造函数。PlugInManagerFactory 只是对 PlugInProviderFactory 进行包装。PlugInFactory 创建并维护这些工厂的一个字典,并将工作委托给相应的工厂,如图 5 中的代码所示。
在这里,需要注意特定于 Plug-In Loader 的命名约定。Enterprise Library 多态集合使用它们所包含项的名称作为键,因此每个名称在集合中必须是唯一的。对于 Plug-In Loader 而言,插件的类型应该是最为直观的键。然而,这可能要求我创建自己的集合类(使用类型作为键),因此我将无法重用 Enterprise Library 提供的类。
由于设计时组件在任何情况下都需要一个唯一名称,Plug-In Loader 的约定适用于要使用插件类型的合格程序集名称进行命名的每个 PlugInProvider,该合格程序集名称便是您在图 5 中看到的名称。这虽然不是一个太好的方法,但是用户绝对不会注意到,因为我还将在设计时组件中对此约定进行处理。在另一方面,如果您喜欢编辑原始 XML,那么所有东西在任何情况下对您而言都只不过是字符串。
这就是创建应用程序块的第一个步骤。如果您返回上文查阅图 1,可能会奇怪为何我没有定义任何装配器类。这是因为装配器依赖于 PlugInProvider 的实现,而不是依赖于抽象的 PlugInProvider 类本身。现在,我将介绍如何定义配置类,然后我将介绍如何实现 PlugInProvider,其中我还将围绕创建相应的装配器展开讨论。
定义插件配置
Enterprise Library 的配置框架建立在 System.Configuration 的基础之上,而且工作方式也与其非常相似。为了定义 Plug-In Loader 的配置节,我创建了 PlugInLoaderSettings 类,如图 6 所示。应用程序块的配置节应该派生自 Microsoft.Practices.EnterpriseLibrary.Common.Configuration.SerializableConfigurationSection,而不是直接从 System.Configuration.ConfigurationSection 派生。这可将 Enterprise Library 功能添加到该类;此外,还允许您在应用程序配置文件之外的其他位置存储配置节。
PlugInLoaderSettings 类仅包含一个 PlugInProviderData 类的集合。PlugInProviderData 类包含用来配置 PlugInProvider 实例的数据,如下所示:
public class PlugInProviderData :NameTypeConfigurationElement { public PlugInProviderData() :base() { } public PlugInProviderData(string name, Type type) : base(name, type) { } }
该类表示一个配置元素,而且不是直接从 System.Configuration.ConfigurationElement 派生。假如我希望创建一个简单的配置元素,我可以直接从 ConfigurationElement 派生 PlugInProviderData,但是 Enterprise Library 还为我提供了另外两种选择。一种选择是 NamedConfigurationElement 类,另一种则是 NameTypeConfigurationElement。前一种选择将一个名称添加到配置元素,该配置元素在实现应用程序块的设计时功能时十分有用。此外,该名称还可作为 Enterprise Library 提供的泛型配置集合类中的唯一键。
NameTypeConfigurationElement 为配置元素增加了附加的 Type 属性,它可用来支持多态集合,而多态集合恰好就是我在本例中所要的东西 —— 以便为不同的插件类型指定不同的插件提供程序(每个均具有唯一的配置设置)。在这里,配置元素的名称作为元素的键,Type 属性则标识元素所配置的类型。对于 Plug-In Loader 而言,Type 属性标识实现 PlugInProvider 的类型。让我们回想一下,按照约定,Plug-In Loader 使用 Name 属性来存储插件的类型的合格程序集名称。很容易弄混这两种类型,但该名称标识应当由提供程序为其提供服务的插件类型,而 Type 属性则标识提供程序的类型。由于名称即为键,因此只需将插件类型定义一次,但是同一个提供程序类型可以为许多不同的插件类型提供服务,事实上,最常见的情况是:它们可能都由同一个提供程序提供服务。
鉴于 Enterprise Library 构造这些配置元素类时使用的方式,我们无法将 PlugInProviderData 类变得抽象,但是您可以将其视为抽象的。请注意,它实际上没有执行任何工作,所以在这种特殊的实现方式中,我可以省略它并为不同的插件提供程序创建我的配置元素,方法是直接从 NameTypeConfigurationElement 派生它们。但是,抽象的 PlugInProvider 类实际也包含某些实现,而且如果在提供程序和它们的配置元素之间存在一对一关系,在理解应用程序块代码的结构时会更容易。
实现 PlugInProvider
到目前为止,运行时组件的抽象框架已经完成了,但是它仍然没有任何功能。现在,到了实现 PlugInProvider 的时间了。这是一种本机实现,因此并不安全,而且不支持能从内存中卸载的插件。所以,我将它称作 NaivePlugInProvider。
如图 7 所示,NaivePlugInProvider 类派生自 PlugInProvider。它的主要功能在 GetPlugIns 方法中实现,该方法简单地加载和反射位于所配置文件夹中的所有程序集中的所有类型。如果某个类型实现了想要的插件类型,便会创建该类型的新实例并添加到返回的插件列表中。请注意,此实现要求所有插件都具有默认构造函数,更健壮的实现可以使用更完善的方法。
NaivePlugInProvider 具有其他两个并不是特别显著的特点,但是在创建 Enterprise Library 应用程序块的上下文中,这两个特点显得十分有趣:使用了 ConfigurationElementType 属性,以及缺少默认构造函数。
在配置 Plug-In Loader 应用程序块时,您只需要关心要使用哪一个 PlugInProvider,而不用关心由哪一个类为该提供程序提供配置数据。ConfigurationElementType 属性包含该信息,这意味着配置数据仅包含有关要创建哪一个 PlugInProvider 的信息,而 Enterprise Library 基础结构则指出了哪一个类包含针对该提供程序的配置数据。在本例中,这个类是 NaivePlugInProviderData 类,如图 8 所示。该类从 PlugInProviderData 派生,并且提供了一个额外的配置属性,允许您指定将包含插件程序集的文件夹。
有关 NaivePlugInProvider 的另一个有趣的事情是:它缺少默认构造函数。如果没有默认构造函数,Enterprise Library 如何创建 NaivePlugInProvider 的新实例?NaivePlugInProviderData 具有一个 Assembler 属性。该属性标识一个可从 NaivePlugInProviderData 实例创建 NaivePlugInProvider 实例的类型。
NaivePlugInProviderAssembler 类也显示在图 8 中。装配器类必须实现包含单个 Assemble 方法的 IAssembler。它使用提供的配置数据取出相关信息并创建新的 NaivePlugInProvider 实例。
现在,Plug-In Loader 包含了一个完全可工作(尽管有些过于简单)的实现,并且可投入使用。您现在可以继续并创建更多提供程序,以便为该应用程序块创建不同的插件发现行为。一种显而易见的扩展便是:一个遵循有关发现和加载插件的安全实践的提供程序。另一种可能的扩展是:一个从 SQL Server 表中的 Blob 中获取插件的提供程序,这可能造成对数据访问应用程序块的可选依赖。
如果不介意必须手动在 XML 文件中写入整个配置,您可以到此为止并发布您的应用程序块了。否则,可以创建一个设计时组件,让它为您完成这个工作。
设计时行为
设计时组件插入到 Enterprise Library 配置应用程序之中。这是一个可扩展 Windows 窗体应用程序,允许您使用丰富的用户界面来编辑应用程序配置文件,而不是必须编写原始 XML 代码。该组件有三项职责:它必须为应用程序 UI 自身提供行为;它必须允许应用程序序列化用户的设置;在用户打开已有的应用程序配置文件时,它必须能够反序列化配置数据。
由于应用程序配置文件是基于 XML 的,因此它们天生便具有层次结构。配置应用程序将此模型化为一个由节点组成的树。运行时组件中的每个配置元素必须由一个设计时节点类来表示,该节点类为配置类提供了额外的设计时行为。图 9 描绘了 Plug-In Loader 应用程序块的这种关系。
图 9 运行时类和设计时类之间的映射
为了与 Enterprise Library 配置应用程序集成,设计时组件必须向其进行注册。程序集及其依赖项必须与配置应用程序本身位于同一目录,而且必须利用 ConfigurationDesignManager 属性对其进行标记,如下所示:
[assembly:ConfigurationDesignManager( typeof(PlugInLoaderConfigurationDesignManager))]
该程序集级别的属性向配置应用程序注册 PlugInLoaderConfigurationDesignManager。该类从抽象的 ConfigurationDesignManager 类派生。
定义设计时行为的工作涉及指定在具体上下文中可能执行的具体操作。可以通过重写 ConfigurationDesignManager 的 Register 方法来达到这个目的:
public override void Register(IServiceProvider serviceProvider) { PlugInLoaderCommandRegistrar cmdRegistrar = new PlugInLoaderCommandRegistrar(serviceProvider); cmdRegistrar.Register(); // 此处添加节点映射代码... }
PlugInLoaderCommandRegistrar 从抽象的 CommandRegistrar 类派生;其目的在于向配置应用程序注册设计时操作。需要实现的第一个操作是将应用程序块添加到应用程序配置文件的命令。当 Plug-In Loader 应用程序块被添加到应用程序配置时,必须将 PlugInLoaderSettingsNode 及其子 PlugInProviderCollectionNode 添加到层次结构中。
首先,必须定义这些节点类,例如:
public class PlugInLoaderSettingsNode :ConfigurationNode { public PlugInLoaderSettingsNode() : base("Plug-In Loader Application Block") {} [ReadOnly(true)] public override string Name { get { return base.Name; } set { base.Name = value; } } }
PlugInProviderCollectionNode 几乎是完全相同的,因为 PlugInLoaderSettingsNode 不包含除 PlugInProviders 集合之外的其他属性。尽管您可能认为我可以为两个节点使用一个公共的类,但这并不适用于本例的情况。两个节点在层次结构中占据不同的位置,而且我将为它们附加不同的操作。您可能奇怪我为何重写 Name 属性 (Property),实际上我只不过使用 Read-only 属性 (Attribute) 对它进行了标记。这将使这些节点在配置应用程序中成为只读的。
当用户调用将 Plug-In Loader 应用程序块添加到应用程序配置文件的该命令时,必须将这两个节点添加到层次结构中。为达到这个目的,我创建了 AddPlugInLoaderSettingsNodeCommand 类,如图 10 所示。它从 AddChildNodeCommand 派生并重写 ExecuteCore 方法,以实现想要的逻辑。该命令类必须与节点类进行关联,以便基类知道它应创建一个 PlugInLoaderSettingsNode 实例并将其添加到层次结构中。在调用 ExecuteCore 的基实现后,已经达到了此目的,所以我所需做的全部工作便是创建一个新的 PlugInProviderCollectionNode 并将其添加到设置节点。
AddPlugInLoaderSettingsNodeCommand 类定义当用户调用该命令时所发生的操作。但我仍然需要定义该命令的使用时间和使用场合。应仅在用户选择应用程序配置根节点时才使用该命令,而且应当只可能调用该命令一次。我通过重写抽象的 Register 方法,在 PlugInLoaderCommandRegistrar 类中实现这个目的:
public override void Register() { this.AddPlugInLoaderCommand(); // 此处添加其他命令... }
AddPlugInLoaderCommand 方法仅包含三条语句,如下所示:
private void AddPlugInLoaderCommand() { ConfigurationUICommand cmd = ConfigurationUICommand.CreateSingleUICommand( this.ServiceProvider, "Plug-In Loader Application Block", "Add the Plug-In Loader Application Block", new AddPlugInLoaderSettingsNodeCommand(this.ServiceProvider), typeof(PlugInLoaderSettingsNode)); this.AddUICommand(cmd, typeof(ConfigurationApplicationNode)); this.AddDefaultCommands(typeof(PlugInLoaderSettingsNode)); }
通过调用 CreateSingleUICommand,我指定该命令只能被调用一次。在该方法调用中,我还提供了显示文本和 AddPlugInLoaderSettingsNodeCommand 的一个实例,当用户选择执行该操作时调用该实例。通过调用 AddUICommand,我将该命令与 ConfigurationApplicatonNode 类型进行了关联,该类型是应用程序配置根节点的类型。AddDefaultCommands 方法将默认命令(例如 Add 和 Delete)添加到新创建的 PlugInLoaderSettingsNode。
提供程序节点
PlugInProviderData 必须由 PlugInProviderNode 进行增强,而 NaivePlugInProviderData 则由 NaivePlugInProviderNode 进行增强,如图 9 所示。图 11 中抽象的 PlugInProviderNode 为 PlugInProviderData 提供了设计时功能。来自 System.ComponentModel 命名空间的几个属性在此处派上了用场:Category、Editor 和 ReadOnly。它们的功能与在 Visual Studio® 属性网格中相同。
图 9 运行时类和设计时类之间的映射
PlugInProviderNode 包装 PlugInProviderData 实例,该实例提供并存储除插件类型之外的所有配置数据。Plug-In Loader 使用特殊的命名约定,按照这种约定,所配置的 PlugInProvider 名称是插件类型的合格程序集名称。因为不能保证 Name 属性会被解析为类型,PlugInProviderNode 在一个成员变量中单独存储插件类型。
PlugInType 属性 (Property) 还包含 BaseType (Attribute),在 Editor (Attribute) 中标识的 TypeSelectorEditor 使用 BaseType 来筛选可用类型。当该编辑器显示可用类型时,只会列出那些抽象的基类型(或接口)。在选择插件类型时,这些是值得列出的仅有类型,因为您无法将插件建立在密封类型的基础之上。
另一个值得注意的 PlugInProviderNode 功能是只读的 Provider 属性。我发现,能够检查配置了哪个提供程序总是一件不错的事情,而且这为向用户通报信息提供了最直接的方式。否则,真的很难告知使用配置应用程序的时机。
关于 PlugInProviderNode,最后一件值得注意的事情是:我使用 OnRenamed 保存节点的名称和被同步的基础数据的名称。
NaivePlugInProviderNode 类通过提供 PlugInFolder 属性来扩展 PlugInProviderNode。与添加应用程序块本身相比,将 NaivePlugInProvider 命令添加到配置应用程序要更为简单一些,因为该命令不需要在 NaivePlugInProviderNode 下创建附加子节点。所以,我不需要为该操作创建单独的命令类。相反,我可以让 PlugInLoaderCommandRegistrar 处理所有的命令注册:
this.AddMultipleChildNodeCommand( "Naive Plug-In Provider", "Add a new naive Plug-In Provider", typeof(NaivePlugInProviderNode), typeof(PlugInProviderCollectionNode)); this.AddDefaultCommands(typeof(NaivePlugInProviderNode));
通过调用 AddMultipleChildNodeCommand,我指定该命令可以被调用任意多次,以创建新的 NaivePlugInProviderNodes 并作为 PlugInProviderCollectionNode 的子节点。也可使用类似机制添加其他 PlugInProvider 节点类型。
序列化和反序列化 XML
当使用配置应用程序时,在选择保存更改之前,设置仅保留在内存中。在保存更改之后,每个节点中配置的数据必须被序列化为 XML。幸运的是,Enterprise Library 和 System.Configuration 负责在配置元素和 XML 之间执行序列化和反序列化。您需要做的唯一工作是指定如何从节点映射到配置类。
可以通过重写 PlugInLoaderConfigurationDesignManager 的 GetConfigurationSectionInfo 方法来达到这个目的。此实现从层次结构中获取 PlugInLoaderSettingsNode,但是将实际工作委托给 PlugInLoaderSettingsBuilder 类,如图 12 所示。该内部类创建一个新的空 PlugInLoaderSettings 实例,然后使用节点层次结构将配置数据添加到该实例。
由于 Plug-In Loader 的配置层次结构非常浅,因此仅需循环通过所有 PlugInProviders 并从它们的节点添加配置数据。如果层次结构较深,此操作可能需要遍历节点层次结构并建立配置类的等效层次结构。
就像 Enterprise Library 和 System.Configuration 负责序列化到 XML 一样,该框架也执行从 XML 到配置类实例的反序列化工作。但是,您必须提供代码,从配置类到节点层次结构进行映射。第一步是重写 PlugInLoaderConfigurationDesignManager 的 OpenCore 方法:
protected override void OpenCore(IServiceProvider serviceProvider, ConfigurationApplicationNode rootNode, ConfigurationSection section) { if (section != null) { PlugInLoaderSettingsNodeBuilder builder = new PlugInLoaderSettingsNodeBuilder(serviceProvider, (PlugInLoaderSettings)section); rootNode.AddNode(builder.Build()); } }
与序列化为 XML 类似,这里我将实际工作委托给另一个类:PlugInLoaderSettingsNodeBuilder。该类从 NodeBuilder 派生,NodeBuilder 为配置类和节点间的映射提供了一些实用工具方法。我的想法是:根节点和所有集合节点拥有关联的节点构建器 (Node Builder) 类,所以节点构建器类仅包含创建它自己的节点类型的代码,而将创建其余节点层次结构的工作委托给其他节点构建器类。这也适用于 PlugInLoaderSettingsNodeBuilder,它创建一个新的 PlugInLoaderSettingsNode 并将创建 PlugInProviders 集合的工作委托给另一个类。本文的下载代码展示了具体的实现方式。
该代码使用 NodeCreationService 从配置数据创建新的 PlugInProviderNode,并且将该节点添加到集合节点。为了使 NodeCreationService 能够执行映射,必须由 PlugInLoaderConfigurationDesignManager 在 Register 方法中注册节点映射。
为了创建和注册节点映射,我创建 PlugInLoaderNodeMapRegistrar 类(派生自 NodeMapRegistrar)并重写它的抽象 Register 方法。在这里,我通过调用继承的 AddMultipleNodeMap 方法,简单地在配置数据类和对应节点类之间建立了映射:
this.AddMultipleNodeMap("Naive Plug-In Provider", typeof(NaivePlugInProviderNode), typeof(NaivePlugInProviderData));
通过使用节点构建器类的机制,我可以建立一个几乎具有任意深度和复杂性的节点层次结构,同时让每个节点构建器类的实现过程仍保持相对简单。这种方法对于本文中的示例来说似乎太过复杂,但是它具有很好的伸缩性,可在更加复杂的配置方案中大显身手。
小结
创建 Enterprise Library 应用程序块的过程并不那么容易,但是对于正确的项目类型而言,它已经被证明是一种物超所值的方法。创建 Enterprise Library 应用程序块(或者说将标准的、配置驱动式的库转化为应用程序块)的核心在于开发运行时组件。但是,可选的额外组件也具有同等的重要性。我介绍了设计时组件,但是这仅仅是若干步骤中的第一步。
将应用程序块打包在 Microsoft® Installer (MSI) 程序包中并不难实现,这使得应用程序块更易于下载和安装。此外,您还应该考虑创建完善的文档 —— 不仅是从 XML 注释生成的 API 文档,还应包括快速介绍、架构概述、入门指南等等。
大多数开发人员通过示例进行学习,所以几个优秀的 QuickStart(快速入门)示例程序将使应用程序块更容易得到采纳。即便您的受众仅限于组织内部,也非常值得为示例投入精力。