zoukankan      html  css  js  c++  java
  • 通过自定义配置实现插“.NET研究”件式设计 狼人:

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

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

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

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

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

    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上海网站建设="color: #000000;">: {
    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"上海闵行企业网站制作an style="color: #000000;">] = 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:
    上海徐汇企业网站设计与制作an>public上海闵行企业网站设计与制作="color: #000000;"> 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。

    声明:此博有部分内容为转载,版权归原作者所有~
  • 相关阅读:
    一行代码更改博客园皮肤
    fatal: refusing to merge unrelated histories
    使用 netcat 传输大文件
    linux 命令后台运行
    .net core 使用 Nlog 配置文件
    .net core 使用 Nlog 集成 exceptionless 配置文件
    Mysql不同字符串格式的连表查询
    Mongodb between 时间范围
    VS Code 使用 Debugger for Chrome 调试vue
    css权重说明
  • 原文地址:https://www.cnblogs.com/waw/p/2216975.html
Copyright © 2011-2022 走看看