一起谈.NET技术,通过自定义配置实现插件式设计

  软件设计有一句话叫做约定优于配置,很多人将其作为拒绝配置的理由。但是,约定和配置的使用,都有个度的问题。我不赞为了所谓的扩展性,为你的应用设计一套只有你自己才能看懂的配置体系。但是,在很多场景中,配置是提供应用灵活度的首要甚至是唯一途径。对于框架的设计者来说,对于配置的驾驭是一项基本的技能。

  可能你很少使用自定义配置,可能你理解的自定义配置仅仅限于AppSetting,不过我想你应该对于System.Configuration这个命名空间下的几个基本的类型有基本的了解。比如ConfigurationSection、ConfigurationElement、ConfigurationElementCollection等。本篇文章不会介绍关于System.Configuration的基础知识,而是通过一个简单的例子为你讲述一些所谓高级的知识点,比如不可识别配置元素的动态解析。(源代码从这里下载)

目录
一、通过自定义配置实现的最终效果
二、相关配置类型的定义
三、两个重要的类型:NameTypeConfigurationElement和NameTypeConfigurationElementCollectionT
四、ResourceProviderFactory的定义
五、补充

  一、通过自定义配置实现的最终效果

  为了让大家对自定义配置的作用有一个深刻的映像,我们先来给出一个简单的例子。我们采用在《.NET的资源并不限于.resx文件,你可以采用任意存储形式》中介绍的关于自定义ResourceManager以实现对多种资源存储形式的支持。现在只关注与资源的读取,我们将基于不同存储形式的资源读取操作实现在相应的ResourceProovider中,它们实现如下一个简单的IResourceProvider接口。

 
  
1 : public interface IResourceProvider
2 : {
3 : object GetObject( string key);
4 : }

  然后我们创建两个具体的ResourceProvider:DbResourceProvider和XmlResourceProvider,它们分别基于数据库表和XML文件的资源存储形式。DbResourceProvider需要连接数据库,需要引用配置的连接字符串,所以有一个ConnectionStringName属性;而XmlResourceProvider需要访问具体的XML文件,FileName属性表示文件路径。

 
  
1 : [ConfigurationElementType( typeof (DbResourceProviderConfigurationElement))]
2 : public class DbResourceProvider : IResourceProvider
3 : {
4 : public string ConnnectionStringName { get ; private set ; }
5 : public DbResourceProvider( string connectionStringName)
6 : {
7 : this .ConnnectionStringName = connectionStringName;
8 : }
9 : public object GetObject( string key)
10 : {
11 : throw new NotImplementedException();
12 : }
13 : public override string ToString()
14 : {
15 : return string .Format( " {0}\n\tConncectionString Name:{1} " , typeof (DbResourceProvider).FullName, this .ConnnectionStringName);
16 : }
17 : }
18 :
19 : [ConfigurationElementType( typeof (XmlResourceProviderConfigurationElement))]
20 : public class XmlResourceProvider : IResourceProvider
21 : {
22 : public string FileName { get ; private set ; }
23 : public XmlResourceProvider( string fileName)
24 : {
25 : this .FileName = fileName;
26 : }
27 : public object GetObject( string key)
28 : {
29 : throw new NotImplementedException();
30 : }
31 : public override string ToString()
32 : {
33 : return string .Format( " {0}\n\tFile Name:{1} " , typeof (XmlResourceProvider).FullName, this .FileName);
34 : }
35 : }

  具体使用哪个ResourceProvider,通过配置来决定。整个配置定义在artech.resources配置节中,该配置节具有一个providers子节点,它定义了一系列ResourceProvider的列表。每个ResourceProvider配置具有两个相同的属性:Name和Type,以及一些自己专属的配置属性(比如DbResourceProvider的connectionStringName,XmlResourceProvider的fileName)。至于默认采用哪个Provider,则通过配置节的defaultProvider属性来决定。在本例中,我们默认采用的是DbProvider。

 
  
1 : ? xml version = " 1.0 " encoding = " utf-8 " ?
2 : configuration
3 : configSections
4 : section name = " artech.resources " type = " Artech.Resources.Configuration.ResourceSettings,Artech.CustomConfiguration " /
5 : / configSections
6 : artech.resources defaultProvider = " DbProvider "
7 : providers
8 : add name = " DbProvider " type = " Artech.Resources.DbResourceProvider, Artech.CustomConfiguration " connectionStringName = " LocalSqlServer " /
9 : add name = " XmlProvider " type = " Artech.Resources.XmlResourceProvider, Artech.CustomConfiguration " fileName = " C:\resources.xml " /
10 : / providers
11 : / artech.resources
12 : / configuration

  现在我们有一个ResourceProviderFactory的工厂类来帮助我们根据配置创建默认的ResourceProvider,或者创建指定名称的ResourceProvider。现在我们按照如下的方式使用ResourceProviderFactory。

 
  
1 : static void Main( string [] args)
2 : {
3 : IResourceProvider resourceProvider = ResourceProviderFactory.GetResourceProvider();
4 : Console.WriteLine(resourceProvider);
5 : Console.WriteLine();
6 :
7 : resourceProvider = ResourceProviderFactory.GetResourceProvider( " XmlProvider " );
8 : Console.WriteLine(resourceProvider);
9 : Console.WriteLine();
10 :
11 : resourceProvider = ResourceProviderFactory.GetResourceProvider( " DbProvider " );
12 : Console.WriteLine(resourceProvider);
13 : Console.WriteLine();
14 : }

  输出结果:

 
  
1 : Artech.Resources.DbResourceProvider
2 : ConncectionString Name:LocalSqlServer
3 :
4 : Artech.Resources.XmlResourceProvider
5 : File Name:C:\resources.xml
6 :
7 : Artech.Resources.DbResourceProvider
8 : ConncectionString Name:LocalSqlServer

  接下来我们就来介绍整个配置体系,以及ResourceProviderFactory的实现。

  二、相关配置类型的定义

  我们现在来看看与配置相关的类型的定义。整个配置节定义在如下一个ResourceSettings的类中,它直接继承自ConfigurationSection。ResourceSettings具有两个配置属性:DefaultProvider和Providers,分别代表artech.resources的defaultProvider属性和providers子节点。

 
  
1 : public class ResourceSettings: ConfigurationSection
2 : {
3 : [ConfigurationProperty( " defaultProvider " , IsRequired = true )]
4 : public string DefaultProvider
5 : {
6 : get { return ( string ) this [ " defaultProvider " ];}
7 : set { this [ " defaultProvider " ] = value;}
8 : }
9 : [ConfigurationProperty( " providers " , IsRequired = true )]
10 : public NameTypeElementCollectionResourceProviderConfigurationElement Providers
11 : {
12 : get { return (NameTypeElementCollectionResourceProviderConfigurationElement) this [ " providers " ];}
13 : set { this [ " providers " ] = value;}
14 : }
15 : public static ResourceSettings GetConfiguration()
16 : {
17 : return (ResourceSettings)ConfigurationManager.GetSection( " artech.resources " );
18 : }
19 : }

  属性Providers是一个名称为NameTypeElementCollectionT的泛型类型。从名称我们不难看出,这是一个集合类型,代表配置的ResourceProvider集合。而基于ResourceProvider的配置定义在如下一个ResourceProviderConfigurationElement抽象类中。该类继承自我们自定义的NameTypeConfigurationElement类型,具有一个CreateProvider抽象方法用于创建相应的ResourceProvider。

 
  
1 : public abstract class ResourceProviderConfigurationElement: NameTypeConfigurationElement
2 : {
3 : public abstract IResourceProvider CreateProvider();
4 : }

  DbResourceProvider和XmlResourceProvider具有各自的ResourceProviderConfigurationElement,分别为DbResourceProviderConfigurationElement和XmlResourceProviderConfigurationElement。

 
  
1 : public class DbResourceProviderConfigurationElement : ResourceProviderConfigurationElement
2 : {
3 : [ConfigurationProperty( " connectionStringName " , IsRequired = true )]
4 : public string ConnectionStringName
5 : {
6 : get { return ( string ) this [ " connectionStringName " ];}
7 : set { this [ " connectionStringName " ] = value;}
8 : }
9 : public override IResourceProvider CreateProvider()
10 : {
11 : return new DbResourceProvider( this .ConnectionStringName);
12 : }
13 : }
14 :
15 : public class XmlResourceProviderConfigurationElement : ResourceProviderConfigurationElement
16 : {
17 : [ConfigurationProperty( " fileName " , IsRequired = true )]
18 : public string FileName
19 : {
20 : get { return ( string ) this [ " fileName " ];}
21 : set { this [ " fileName " ] = value;}
22 : }
23 : public override IResourceProvider CreateProvider()
24 : {
25 : return new XmlResourceProvider( this .FileName);
26 : }
27 : }

  三、两个重要的类型:NameTypeConfigurationElement和NameTypeConfigurationElementCollectionT

  接下来介绍两个重要的类型,第一个是ResourceProviderConfigurationElement的基类:NameTypeConfigurationElement。顾名思义,NameTypeConfigurationElement就是具有两个基本配置属性Name和Type的配置元素(ConfigurationElement),其定义如下。方法DeserializeElement定义出来用于解决非识别配置项的反序列化问题。

 
  
1 : public class NameTypeConfigurationElement : ConfigurationElement
2 : {
3 : [ConfigurationProperty( " name " , IsRequired = true , IsKey = true )]
4 : public string Name
5 : {
6 : get { return ( string ) this [ " name " ];}
7 : set { this [ " name " ] = value;}
8 : }
9 : [ConfigurationProperty( " type " , IsRequired = true )]
10 : public string TypeName
11 : {
12 : get { return ( string ) this [ " type " ];}
13 : set { this [ " type " ] = value;}
14 : }
15 : public Type Type
16 : {
17 : get { return Type.GetType( this .TypeName);}
18 : }
19 : public void DeserializeElement(XmlReader reader)
20 : {
21 : base .DeserializeElement(reader, false );
22 : }
23 : }

  另一个类型就是NameTypeConfigurationElement的配置元素集合(ConfigurationElementCollection):NameTypeElementCollectionT。应该说它是整个配置体系的核心,其全部定义如下所示。

 
  
1 : public class NameTypeElementCollectionT : ConfigurationElementCollection where T : NameTypeConfigurationElement
2 : {
3 : protected override ConfigurationElement CreateNewElement()
4 : {
5 : return Activator.CreateInstanceT();
6 : }
7 : protected override object GetElementKey(ConfigurationElement element)
8 : {
9 : return (element as NameTypeConfigurationElement).Name;
10 : }
11 : protected virtual Type RetrieveConfigurationElementType(XmlReader reader)
12 : {
13 : Type configurationElementType = null ;
14 : if (reader.AttributeCount 0 )
15 : {
16 : for ( bool go = reader.MoveToFirstAttribute(); go; go = reader.MoveToNextAttribute())
17 : {
18 : if ( " type " .Equals(reader.Name))
19 : {
20 : Type providerType = Type.GetType(reader.Value, false );
21 : Attribute attribute = Attribute.GetCustomAttribute(providerType, typeof (ConfigurationElementTypeAttribute));
22 : if (attribute == null )
23 : {
24 : throw new ConfigurationErrorsException( " No ConfigurationElementTypeAttribute is applied. " );
25 : }
26 : configurationElementType = ((ConfigurationElementTypeAttribute)attribute).ConfigurationElementType;
27 : break ;
28 : }
29 : }
30 : reader.MoveToElement();
31 : }
32 : return configurationElementType;
33 : }
34 : protected override bool OnDeserializeUnrecognizedElement( string elementName, XmlReader reader)
35 : {
36 : if ( base .AddElementName.Equals(elementName))
37 : {
38 : Type configurationElementType = this .RetrieveConfigurationElementType(reader);
39 : var currentElement = (T)Activator.CreateInstance(configurationElementType);
40 : currentElement.DeserializeElement(reader);
41 : base .BaseAdd(currentElement, true );
42 : return true ;
43 : }
44 : return base .OnDeserializeUnrecognizedElement(elementName, reader);
45 : }
46 : public T GetConfigurationElement( string name)
47 : {
48 : return (T) this .BaseGet(name);
49 : }
50 : }

  对于配置我们应该有这样的认识:我们通过相应的类型来定义配置文件中的某个XML元素,在进行读取的时候实际上就是一个反序列化的工作。而对于成功进行序列化和反序列化,其根本前提是确定目标类型,因为类型描述了元数据。带着这个结论再来看看我们的以XML表示的配置和ResourceSettings的定义,我们会发现一个问题:ResourceSetting的Providers属性的类型是NameTypeElementCollectionResourceProviderConfigurationElement,配置元素类型ResourceProviderConfigurationElement是一个抽象类型。而我们需要将具体的ResourceProvider配置反序列化成DbResourceProviderConfigurationElement和XmlResourceProviderConfigurationElement,而整个配置系统似乎找不到这个两个类型的影子。如果不能预先确定配置元素需要反序列化成的真实类型,整个配置的读取将会失败。具体来说,它不能识别DbProvider元素的connectionStringName属性,和XmlProvider的fileName属性,因为基类ResourceProviderConfigurationElement没有相关属性的定义。

  既然在默认情况下具体ResourceProvider的配置元素不能被反序列化,它们属于不可识别元素(Unrecognized Element),那么我们只要手工对其实施反序列化,具体做法就是重写ConfigurationElementCollection的OnDeserializeUnrecognizedElement方法。但是即使手工进行反序列化,也需要确定具体的配置元素类型,这又如何解决呢?如果你足够仔细的话,在定义DbResourceProvider和XmlResourceProvider的时候,在类上面应用了一个特殊的自定义特性:ConfigurationElementTypeAttribute,它建立起了具体ResourceProvider和对应配置元素之间的匹配关系。

 
  
1 : [ConfigurationElementType( typeof (DbResourceProviderConfigurationElement))]
2 : public class DbResourceProvider : IResourceProvider
3 : {
4 : // ...
5 : }

  而这个ConfigurationElementTypeAttribute定义非常简单,仅仅定义一个用于表示配置元素类型的ConfigurationElementType属性,该属性在构造函数中初始化。

 
  
1 : [AttributeUsage( AttributeTargets.Class)]
2 : public class ConfigurationElementTypeAttribute: Attribute
3 : {
4 : public Type ConfigurationElementType { get ; private set ; }
5 : public ConfigurationElementTypeAttribute(Type configurationElementType)
6 : {
7 : this .ConfigurationElementType = configurationElementType;
8 : }
9 : }

  由于每个具体的ResourceProvider都具有这样一个ConfigurationElementTypeAttribute来指定对应的ConfigurationElement类型,那么我们就可以反射来为反序列化确定配置元素的目标类型了。这样的操作实现在RetrieveConfigurationElementType方法中。

  四、ResourceProviderFactory的定义

  NameTypeElementCollectionT通过重写OnDeserializeUnrecognizedElement方法,以及借助于ConfigurationElementTypeAttribute特性,解决了对不可识别元素的解析问题。而具体的ResourceProviderConfigurationElement都实现了CreateProvider方法来创建对应的ResourceProvider,那么ResourceProviderFactory的实现就非常简单了。

 
  
1 : public static class ResourceProviderFactory
2 : {
3 : public static IResourceProvider GetResourceProvider()
4 : {
5 : ResourceSettings settings = ResourceSettings.GetConfiguration();
6 : return GetResourceProvider(settings.DefaultProvider);
7 : }
8 : public static IResourceProvider GetResourceProvider( string name)
9 : {
10 : ResourceSettings settings = ResourceSettings.GetConfiguration();
11 : return settings.Providers.GetConfigurationElement(name).CreateProvider();
12 : }
13 : }

  五、补充

  经常关注我博客朋友应该知道本人对微软开源框架EnterLib有一定的了解。熟悉EnterLib的朋友经常诟病于它繁琐的配置,这确实是一个问题。不过这从另一个方面说明了EnterLib底层配置系统的强大,不然很难支持如此复杂的配置。对于学习自定义配置,了解EnterLib配置体系的实现是一个不错的途径。实际上,本篇文章关于不可识别配置元素的解析的解决方案就是来源于EnterLib。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值