Enterprise Library 自定义应用程序块实战(上)
1 概述
Enterprise Library 由 Microsoft 的“模式和实践”团队创建,以助于解决开发人员在大多数情况下所面临的普通开发问题,是一个应用程序块的集合。虽然 Enterprise Library 包含了许多应用程序块,但在许多情况下依然无法满足特定的需求。本文介绍了如何构建一个自己的应用程序块来满足自己的需要。
本文仅是所使用的示例代码来自 Mark Seemann 的《利用 Enterprise Library 自定义应用程序块加快开发速度》(针对 Enterprise Library 3.1 进行了必要的修改),但采用了不同的构建方式来进行叙述,原文采用的是从需求开始自上而下的方式讲解,而本文则从下而上逐步构建,以求使整个构建过程更加 清楚而简单。同时,在构建的过程中加入了对所使用到的 Enterprise Library 所包含的对象进行了必要的解释。
2 需求
创建一个可用于管理应用程序插件的应用程序块,实际实现的是获取已安装的插件列表功能。
3 配置的 Scheme
Enterprise Library 的应用程序块是由配置驱动的,所以,首先应该考虑一下如何保存配置信息。先看看所需要的配置文件。
4 基本配置类
根据上面的配置文件,我们可以就可以定义出所需要的插件管理的配置类和提供程序数据的配置类如下:
插件管理配置类:
在说明这二个类之前,先解释一下其中几个相关的类:
因此,可以知道配置类 PlugInLoaderSettings 是一个可序列化的配置类,同时,它定义了配置节的名称 "plugInConfiguration" 和插件配置集合的名称 "plugInProviders",并通过用 ConfigurationPropertyAttribute 类标识以相应的属性提供插件提供程序的集合。因为插件提供程序的配置仅需要 name 和 value 属性,所以直接从 NameTypeConfigurationElement 类继承即可。
在这里,配置类也可以不从 SerializableConfigurationSection 和 NameTypeConfigurationElement ,而是可以从 .NET Framework 2.0 中相应的配置节和配置数据类派生,在此仅是为了能利用 Enterprise Library 的增强功能而已。
5 抽象提供程序基类
在定义好所需要的配置后,就可以定义提供程序的基类了,如下所示:
6 定制的提供程序工厂类
接上一小节,PlugInProviderCustomFactory 即是 Enterprise Library 用于创建 PlugInProvider 对象的工厂类,它派生自 AssemblerBasedCustomFactory 。在工厂类中,实现的是用于从配置文件(配置源)中如何读取配置信息并创建所需要的配置数据类 PlugInProviderData 的对象。如下所示:
7 泛型辅助类的实现
在此,实现了二个辅助类:PlugInManager<T> 实现对 PlugInProvider 的封装;PlugInManagerFactory<T> 实现对 PlugInProviderFactory 类的封装。因为在这二个类的实现中并没有使用特殊的方式,所以在此不在详细解释,仅列出相应的代码如下:
8 客户端接口
在底层都编写好以后,我们就需要为客户端代码提供一个工厂类,以让客户端更方便的使用,PlugInFactory。
同样,实现真正的插件提供程序也需要三个步骤:
到此,我们已经创建了一个完整的应用程序块,如果愿意手工编写配置文件,就可以根据第3小节给出的 Scheme 进行配置后使用了。add 元素中配置相应的插件提供程序,name 任意,不重复即可,type 即为第9小节中实现的提供程序的类型。
下一回,我们将解释如何创建以上程序块的设计时组件,以便我们可以使用 Enterprise Library 配置控制台来从 GUI 中进行配置。
Enterprise Library 由 Microsoft 的“模式和实践”团队创建,以助于解决开发人员在大多数情况下所面临的普通开发问题,是一个应用程序块的集合。虽然 Enterprise Library 包含了许多应用程序块,但在许多情况下依然无法满足特定的需求。本文介绍了如何构建一个自己的应用程序块来满足自己的需要。
本文仅是所使用的示例代码来自 Mark Seemann 的《利用 Enterprise Library 自定义应用程序块加快开发速度》(针对 Enterprise Library 3.1 进行了必要的修改),但采用了不同的构建方式来进行叙述,原文采用的是从需求开始自上而下的方式讲解,而本文则从下而上逐步构建,以求使整个构建过程更加 清楚而简单。同时,在构建的过程中加入了对所使用到的 Enterprise Library 所包含的对象进行了必要的解释。
2 需求
创建一个可用于管理应用程序插件的应用程序块,实际实现的是获取已安装的插件列表功能。
3 配置的 Scheme
Enterprise Library 的应用程序块是由配置驱动的,所以,首先应该考虑一下如何保存配置信息。先看看所需要的配置文件。
<configuration>
<configSections>
<section name="plugInConfiguration" type="PlugInLoader.Configuration.PlugInLoaderSettings, PlugInLoader, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</configSections>
<plugInConfiguration>
<plugInProviders>
<add name="" type="" />
</plugInProviders>
</plugInConfiguration>
</configuration>
在
配置文件中,有一个用于插件管理的配置节 plugInConfiguration ,它的子元素 <plugInProviders>
中定义的即为各种插件的提供程序的定义。插件提供程序由 <add> 元素定义,其中 name 为提供程序的标识名称,type
为提供程序的类型,每一种提供程序可以管理相应插件(在本例中仅实现了相应类型插件的列表获取)。<configSections>
<section name="plugInConfiguration" type="PlugInLoader.Configuration.PlugInLoaderSettings, PlugInLoader, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</configSections>
<plugInConfiguration>
<plugInProviders>
<add name="" type="" />
</plugInProviders>
</plugInConfiguration>
</configuration>
4 基本配置类
根据上面的配置文件,我们可以就可以定义出所需要的插件管理的配置类和提供程序数据的配置类如下:
插件管理配置类:
public class PlugInLoaderSettings : SerializableConfigurationSection
{
public const string SectionName = "plugInConfiguration";
private const string providersProperty_ = "plugInProviders";
public PlugInLoaderSettings() : base() { }
[ConfigurationProperty(PlugInLoaderSettings.providersProperty_)]
public NameTypeConfigurationElementCollection<PlugInProviderData, PlugInProviderData> PlugInProviders
{
get
{
return (NameTypeConfigurationElementCollection<PlugInProviderData, PlugInProviderData>)this[PlugInLoaderSettings.providersProperty_];
}
}
}
提供程序配置类:{
public const string SectionName = "plugInConfiguration";
private const string providersProperty_ = "plugInProviders";
public PlugInLoaderSettings() : base() { }
[ConfigurationProperty(PlugInLoaderSettings.providersProperty_)]
public NameTypeConfigurationElementCollection<PlugInProviderData, PlugInProviderData> PlugInProviders
{
get
{
return (NameTypeConfigurationElementCollection<PlugInProviderData, PlugInProviderData>)this[PlugInLoaderSettings.providersProperty_];
}
}
}
public class PlugInProviderData : NameTypeConfigurationElement
{
public PlugInProviderData()
: base()
{
}
public PlugInProviderData(string name, Type type)
: base(name, type)
{
}
}
{
public PlugInProviderData()
: base()
{
}
public PlugInProviderData(string name, Type type)
: base(name, type)
{
}
}
在说明这二个类之前,先解释一下其中几个相关的类:
- SerializableConfigurationSection。 这是插件配置管理类所继承的类。来自 Enterprise Library 的命名空间:Microsoft.Practices.EnterpriseLibrary.Common.Configuration。它表示了一个可 被序列化和反序列化的配置节。提供了序列化和反序列化的功能。
- ConfigurationPropertyAttribute 。以声明的方式指示所标识的属性是一个配置属性,并指定了配置属性的名称。来自.NET Framework 2.0 的 System.Configuration 命名空间。
- NameTypeConfigurationElement 。命名空间:Microsoft.Practices.EnterpriseLibrary.Common.Configuration 。表示具有 name 和 value 属性的配置元素,这正是我们的插件提供程序的配置所需要的。
- NameTypeConfigurationElementCollection
。NameTypeConfigurationElement 元素的集合,它带有二个泛型参数,第一个是集合中的
NameTypeConfigurationElement
对象的类型,第二个是用于集合中定制配置元素的对象类型。第二个类型必须可以自动转化为第一个类型。
因此,可以知道配置类 PlugInLoaderSettings 是一个可序列化的配置类,同时,它定义了配置节的名称 "plugInConfiguration" 和插件配置集合的名称 "plugInProviders",并通过用 ConfigurationPropertyAttribute 类标识以相应的属性提供插件提供程序的集合。因为插件提供程序的配置仅需要 name 和 value 属性,所以直接从 NameTypeConfigurationElement 类继承即可。
在这里,配置类也可以不从 SerializableConfigurationSection 和 NameTypeConfigurationElement ,而是可以从 .NET Framework 2.0 中相应的配置节和配置数据类派生,在此仅是为了能利用 Enterprise Library 的增强功能而已。
5 抽象提供程序基类
在定义好所需要的配置后,就可以定义提供程序的基类了,如下所示:
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
using System;
using System.Collections;
namespace PlugInLoader
{
[CustomFactory(typeof(PlugInProviderCustomFactory))]
public abstract class PlugInProvider
{
private Type plugInType_;
protected PlugInProvider(Type plugInType)
{
this.plugInType_ = plugInType;
}
public abstract IList GetPlugIns();
protected Type PlugInType
{
get { return this.plugInType_; }
}
}
}
在此,我们就能看到一个提供程序所具有的功能了:GetPlugIns()
方法就是需求所要求的,插件提供程序提供获取该类型插件的列表。提供程序类使用了属性类 CustomFactoryAttribute
进行标识。此属性用于指定 Enterprise Library 应该用什么工厂类来创建它所标识的类,在此我们使用的是
PlugInProviderCustomFactory ,我们将在下一小节解释它。using System;
using System.Collections;
namespace PlugInLoader
{
[CustomFactory(typeof(PlugInProviderCustomFactory))]
public abstract class PlugInProvider
{
private Type plugInType_;
protected PlugInProvider(Type plugInType)
{
this.plugInType_ = plugInType;
}
public abstract IList GetPlugIns();
protected Type PlugInType
{
get { return this.plugInType_; }
}
}
}
6 定制的提供程序工厂类
接上一小节,PlugInProviderCustomFactory 即是 Enterprise Library 用于创建 PlugInProvider 对象的工厂类,它派生自 AssemblerBasedCustomFactory 。在工厂类中,实现的是用于从配置文件(配置源)中如何读取配置信息并创建所需要的配置数据类 PlugInProviderData 的对象。如下所示:
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
using PlugInLoader.Configuration;
namespace PlugInLoader
{
public class PlugInProviderCustomFactory : AssemblerBasedCustomFactory<PlugInProvider,PlugInProviderData>
{
protected override PlugInProviderData GetConfiguration(string name, IConfigurationSource configurationSource)
{
PlugInLoaderSettings settings = (PlugInLoaderSettings)configurationSource.GetSection(PlugInLoaderSettings.SectionName);
return settings.PlugInProviders.Get(name);
}
}
}
其中基类所使用的泛型仅为了实现强类型。在 GetConfiguration 方法中,name 参数所传入的是提供程序配置的名称,即 add 元素中的 name ;configurationSource 参数则是配置源信息。此方法由 Enterprise Library 自动根据 CustomFactoryAttribute 属性的绑定信息进行调用。
除了 PlugInProviderCustomFactory 获取配置信息以外,我们还需要另一个类,即真正创建提供程序的工厂类 PlugInProviderFactory 。代码如下:using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
using PlugInLoader.Configuration;
namespace PlugInLoader
{
public class PlugInProviderCustomFactory : AssemblerBasedCustomFactory<PlugInProvider,PlugInProviderData>
{
protected override PlugInProviderData GetConfiguration(string name, IConfigurationSource configurationSource)
{
PlugInLoaderSettings settings = (PlugInLoaderSettings)configurationSource.GetSection(PlugInLoaderSettings.SectionName);
return settings.PlugInProviders.Get(name);
}
}
}
其中基类所使用的泛型仅为了实现强类型。在 GetConfiguration 方法中,name 参数所传入的是提供程序配置的名称,即 add 元素中的 name ;configurationSource 参数则是配置源信息。此方法由 Enterprise Library 自动根据 CustomFactoryAttribute 属性的绑定信息进行调用。
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
namespace PlugInLoader
{
public class PlugInProviderFactory : NameTypeFactoryBase<PlugInProvider>
{
public PlugInProviderFactory(IConfigurationSource configSource)
: base(configSource) { }
}
}
由代码我们可以看到, PlugInProviderFactory 派生自 NameTypeFactoryBase 类,基类实现了 name/type 对配置信息所对应的对象的自动创建,如果需要更复杂的处理,可以覆写其 New() 方法。因为我们的提供程序的配置是 name/type 对,所以只需要实现一个构造函数即可。
应该来说,到了这里,我们已经可以使用:using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
namespace PlugInLoader
{
public class PlugInProviderFactory : NameTypeFactoryBase<PlugInProvider>
{
public PlugInProviderFactory(IConfigurationSource configSource)
: base(configSource) { }
}
}
由代码我们可以看到, PlugInProviderFactory 派生自 NameTypeFactoryBase 类,基类实现了 name/type 对配置信息所对应的对象的自动创建,如果需要更复杂的处理,可以覆写其 New() 方法。因为我们的提供程序的配置是 name/type 对,所以只需要实现一个构造函数即可。
IConfigurationSource configSource = ConfigurationSourceFactory.Create();
string providerConfigName = "configName" ;//此为配置节中 add 元素的 name 。
string providerConfigName = "configName" ;//此为配置节中 add 元素的 name 。
PlugInProviderFactory factory = new PlugInProviderFactory(configSource );
PlugInProvider provider = factory.Create( providerConfigName ) ;
来创建提供程序对象。如果不需要使用泛型,到此,基本的程序块运行时就完成了,当然真正的提供程序还没有实现,即 PlugInProvider 的子类的实现。为了能实现对泛型、强类型的支持,就还需要实现一些辅助类。7 泛型辅助类的实现
在此,实现了二个辅助类:PlugInManager<T> 实现对 PlugInProvider 的封装;PlugInManagerFactory<T> 实现对 PlugInProviderFactory 类的封装。因为在这二个类的实现中并没有使用特殊的方式,所以在此不在详细解释,仅列出相应的代码如下:
- PlugInManager<T> 类
using System.Collections;
using System.Collections.Generic;
namespace PlugInLoader
{
public class PlugInManager<T>
{
private PlugInProvider provider_;
internal PlugInManager(PlugInProvider provider)
{
this.provider_ = provider;
}
public IList<T> GetPlugIns()
{
IList plugIns = this.provider_.GetPlugIns();
List<T> returnList = new List<T>(plugIns.Count);
foreach (T item in plugIns)
{
returnList.Add(item);
}
return returnList;
}
}
}
using System.Collections.Generic;
namespace PlugInLoader
{
public class PlugInManager<T>
{
private PlugInProvider provider_;
internal PlugInManager(PlugInProvider provider)
{
this.provider_ = provider;
}
public IList<T> GetPlugIns()
{
IList plugIns = this.provider_.GetPlugIns();
List<T> returnList = new List<T>(plugIns.Count);
foreach (T item in plugIns)
{
returnList.Add(item);
}
return returnList;
}
}
}
- PlugInManagerFactory<T> 类
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
namespace PlugInLoader
{
public class PlugInManagerFactory<T>
{
private PlugInProviderFactory factory_;
public PlugInManagerFactory(IConfigurationSource configurationSource)
{
this.factory_ = new PlugInProviderFactory(configurationSource);
}
public PlugInManager<T> Create()
{
string name = typeof(T).AssemblyQualifiedName;
return new PlugInManager<T>(this.factory_.Create(name));
}
}
}
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
namespace PlugInLoader
{
public class PlugInManagerFactory<T>
{
private PlugInProviderFactory factory_;
public PlugInManagerFactory(IConfigurationSource configurationSource)
{
this.factory_ = new PlugInProviderFactory(configurationSource);
}
public PlugInManager<T> Create()
{
string name = typeof(T).AssemblyQualifiedName;
return new PlugInManager<T>(this.factory_.Create(name));
}
}
}
8 客户端接口
在底层都编写好以后,我们就需要为客户端代码提供一个工厂类,以让客户端更方便的使用,PlugInFactory。
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using System;
using System.Collections.Generic;
using System.Text;
namespace PlugInLoader
{
public static class PlugInFactory
{
private readonly static Dictionary<Type, object> factories_ = new Dictionary<Type, object>();
public static PlugInManager<T> GetManager<T>()
{
if (!factories_.ContainsKey(typeof(T)))
{
IConfigurationSource configSource = ConfigurationSourceFactory.Create();
PlugInManagerFactory<T> factory = new PlugInManagerFactory<T>(configSource);
factories_.Add(typeof(T), factory);
}
object obFactory = factories_[typeof(T)];
return ((PlugInManagerFactory<T>)obFactory).Create();
}
}
}
在此实现的功能即是第6小节结束时的示例代码所描述的功能。
9 实现真正的插件提供程序using System;
using System.Collections.Generic;
using System.Text;
namespace PlugInLoader
{
public static class PlugInFactory
{
private readonly static Dictionary<Type, object> factories_ = new Dictionary<Type, object>();
public static PlugInManager<T> GetManager<T>()
{
if (!factories_.ContainsKey(typeof(T)))
{
IConfigurationSource configSource = ConfigurationSourceFactory.Create();
PlugInManagerFactory<T> factory = new PlugInManagerFactory<T>(configSource);
factories_.Add(typeof(T), factory);
}
object obFactory = factories_[typeof(T)];
return ((PlugInManagerFactory<T>)obFactory).Create();
}
}
}
在此实现的功能即是第6小节结束时的示例代码所描述的功能。
同样,实现真正的插件提供程序也需要三个步骤:
- 从 PlugInProviderData 派生出配置类;
- 继承 PlugInProvider 类,实现真正的提供程序;
- 实现 IAssembler 接口,以提供一个从配置类创建真正的插件提供程序的工厂类。
- 分别用 AssemblerAttribute 属性类将工厂类绑定到配置类,用 ConfigurationElementTypeAttribute 属性类将配置类绑定到提供程序类,以让 Enterprise Library 能真正处理这一切。
到此,我们已经创建了一个完整的应用程序块,如果愿意手工编写配置文件,就可以根据第3小节给出的 Scheme 进行配置后使用了。add 元素中配置相应的插件提供程序,name 任意,不重复即可,type 即为第9小节中实现的提供程序的类型。
下一回,我们将解释如何创建以上程序块的设计时组件,以便我们可以使用 Enterprise Library 配置控制台来从 GUI 中进行配置。