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

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

目录
一、通过自定义配置实现的最终效果
二、相关配置类型的定义
三、两个重要的类型:NameTypeConfigurationElement和NameTypeConfigurationElementCollection<T>
四、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 NameTypeElementCollection<ResourceProviderConfigurationElement> Providers
  11:     {
  12:         get{return (NameTypeElementCollection<ResourceProviderConfigurationElement>)this["providers"];}
  13:         set{this["providers"] = value;}
  14:     }
  15:     public static ResourceSettings GetConfiguration()
  16:     {
  17:         return (ResourceSettings)ConfigurationManager.GetSection("artech.resources");
  18:     }
  19: }

属性Providers是一个名称为NameTypeElementCollection<T>的泛型类型。从名称我们不难看出,这是一个集合类型,代表配置的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和NameTypeConfigurationElementCollection<T>

接下来介绍两个重要的类型,第一个是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):NameTypeElementCollection<T>。应该说它是整个配置体系的核心,其全部定义如下所示。

   1: public class NameTypeElementCollection<T> : ConfigurationElementCollection where T : NameTypeConfigurationElement
   2: {
   3:     protected override ConfigurationElement CreateNewElement()
   4:     {
   5:         return Activator.CreateInstance<T>();
   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属性的类型是NameTypeElementCollection<ResourceProviderConfigurationElement>,配置元素类型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的定义

NameTypeElementCollection<T>通过重写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。

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文链接

通过自定义配置实现插件式设计相关推荐

  1. 插件式设计的架构模型与实例

    插件式设计近年来非常流行,其中eclipse起了推波助澜的作用,提到插件式就会不由自主的想到eclipse.其实插件式设计并不是什么新事物,早在几十年前就有了.像X Server就是基于插件式设计的, ...

  2. Docker 的插件式设计

    http://www.tuicool.com/articles/MnIRZvJ http://uzhima.com/2016/08/02/what-is-docker-volume-plugin/ 在 ...

  3. 插件式架构设计实践:插件式系统架构设计简介

    本系列博文将使用微软RIA技术解决方案Silverlight以及扩展性管理框架Managed Extensibility Framework(MEF),以插件式架构设计为导线,分享本人在从事基于微软S ...

  4. 插件式架构设计实践一:插件式系统架构设计简介

    本系列博文将使用微软RIA技术解决方案Silverlight以及扩展性管理框架Managed Extensibility Framework(MEF),以插件式架构设计为导线,分享本人在从事基于微软S ...

  5. Thinkphp多用户B2B2C商城源码带安装视频 系统主要功能采用高内聚,辅助功能插件式实现

    Thinkphp5多用户B2B2C商城源码+安装视频 源码分享,需要源码学习可私信我. 系统概述:本系统是基于ThinkPHP 5.1.34搭建的多商户电商平台,是目前完善度领先的电商管理平台.系统主 ...

  6. 插件式可扩展架构设计心得(干货)

    点击上方 程序员成长指北,关注公众号 回复1,加入高级 Node 进阶交流群 引子 大家可能不知道,鄙人之前人送外号"过度设计".作为一个自信的研发人员,我总是希望我开发的系统可以 ...

  7. VS Code 安装插件、自定义模板、自定义配置参数、自定义主题、配置参数说明、常用的扩展插件

    1. 下载和官网教程 下载地址:https://code.visualstudio.com/ 官方教程:https://code.visualstudio.com/docs 2. 安装插件 安装扩展插 ...

  8. 【java笔记-006】【uni-app】当前运行的基座不包含原生插件[xxx],请在manifest中配置该插件,重新制作包括该原生插件的自定义运行基座

    uni-app引入原生插件的步骤如下:https://nativesupport.dcloud.net.cn/NativePlugin/course/android 将制作好的原生安卓插件包 复制到 ...

  9. vue中is属性搭配vuedraggable插件实现可拖动可视化大屏展示组件的自定义配置功能

    最近有这样一个需求,将大屏上展示的东西都封装成独立的组件让用户自己可以自定义配置自己的组件位置及想要展示的组件,第一个我就想到通过is来实现,分享下我的思路及部分代码供大家参考. 先看下大概布局: 如 ...

最新文章

  1. linux用户及权限
  2. python培训班排行榜-西安Python培训班排行榜
  3. 20165305 苏振龙 《Java 程序设计》第一次测试总结
  4. sklearn综合示例7:集成学习与随机森林
  5. ubuntu18安装python3.6.8_ubuntu 18.04 + Python 3.6.8 更换软件安装源
  6. HTML怎么让img 等比例缩放
  7. [Ruby on Rails系列]3、初试Rails:使用Rails开发第一个Web程序
  8. 正则表达式案例分析 (二)
  9. 【解题报告】Vijos1143 三取方格数
  10. sap系统webservice接口开发
  11. 毫米波雷达信号处理系统
  12. Math.floor cei round
  13. 四旋翼无人机的三维动态Matlab仿真
  14. Mac录屏想只要电脑内置的声音
  15. 古有穷书生街中弄笔,今有弄潮儿网上卖字
  16. 循环队列(队列头尾相接的顺序存储结构)
  17. 基于Qt的智能车载系统嵌入式项目(正点原子IMX6ULL开发板)
  18. 网站权重是什么意思?
  19. 用Matlab画散点图
  20. 中庸----做人的智慧

热门文章

  1. 阿里云mysql 日志_mysql日志-阿里云开发者社区
  2. 2019人工智能实战 第六次作业 段峙宇
  3. R作图-----北京市2017年一季度AQI指数日历热图
  4. 20155230 2016-2017-2 《Java程序设计》第十周学习总结
  5. 准备树莓派下的模块开发环境
  6. 无法打开登录所请求的数据库DbName 。登录失败。 用户 'IIS APPPOOL\DefaultAppPool' 登录失败。 的解决方案...
  7. linux驱动的入口函数module_init的加载和释放(转)
  8. EveryEeek English(9.21)
  9. MFC之AfxbeginThread 线程 创建、挂起、释放、结束、退出
  10. mysql left/right join算法效率分析_mysql left join,right join,inner join超详细用法分析