本文共 8050 字,大约阅读时间需要 26 分钟。
现代软件开发中,各种技术、技巧越来越依赖配置,譬如客户端对用户体验的个性化设置、系统的各种运行时参数设置、可插拔的插件机制、基于配置的IoC架构模式等。配置方式也从最初的二进制存储格式逐步过度到INI文本格式直至今时所广泛使用的Xml格式。使用Xml格式进行配置,大大提高了对设置数据的表现能力,但是在 .NET 1.x 中对Xml配置的操控还有诸多不便,尤其是对Xml配置的存储同步机制很不完善,而从 .NET 2.0 开始,框架提供了更丰富和易于操控使用的机制。
.NET 中的配置文件(Xml)必须以“<configuration>”为根节点,配置文件分为两大部分:配置声明区和数据设置区。
数据设置区可以是用户定义的任意结构层次,但是其“根节点”必须预先在设置声明区定义,运行时会进行有效性检测,一旦发现没有声明的配置节点则会产生一个运行时配置异常。
范例配置文件在.NET 2.0中实现自定义配置,可以使用编程或声明性(属性化)代码编写模型创建自定义配置节。
配置文件中的元素称为 基本XML元素 或 节。基本元素只是具有相关属性(如果有)的简单 XML 标记。节最简单的形式与基本元素一致。而复杂的节可以包括一个或多个基本元素、元素的集合以及其他节。
ConfigurationProperty 类表示配置节点中的特性(Attribute)或它的子元素,我们要做的就是通过 ConfigurationProperty 类将配置实体类中的属性(Property)映射到对应的节点特性(Attribute)。
要在配置文件中使用自定义的配置数据,必须在声明区通过<section>节点对自定义配置节处理程序进行声明,该节点有两个必需属性:name 和 type。
范例配置文件中的配置声明定义如: <section name="dataSystems" type="SWSystem.Data.Configuration.DataSystemsSection, SWSystem.Data" /> 则表示数据设置区中的<dataSystems>节点由 SWSystem.Data.Configuration.DataSystemsSection 类进行处理,并且该类位于 SWSystem.Data 程序集中。
在 .NET 1.x 中要实现自定义的配置处理程序类,其必须实现 IConfigurationSectionHandler 接口,现在 .NET 2.0 中只需要将你的处理程序类继承自 ConfigurationSection 类即可。其处理步骤大致如下:
最终,我们的范例配置处理程序类看起来可能是这样(如果你使用声明模式则代码看起来没有这么麻烦,这些配置属性类将由框架运行时帮你反射生成,正因为如此,所以它的运行时效率要差些。):
public class DataSystemsSection : ConfigurationSection
{ private static readonly ConfigurationProperty _dataSystems = new ConfigurationProperty(null, typeof(DataSystemElementCollection), null, ConfigurationPropertyOptions.IsDefaultCollection); private static ConfigurationPropertyCollection _properties = new ConfigurationPropertyCollection(); static DataSystemsSection() { _properties.Add(_dataSystems); } public DataSystemElementCollection DataSystems { get { return (DataSystemElementCollection)base[_dataSystems]; } } protected override ConfigurationPropertyCollection Properties { get { return _properties; } }}为所有的配置节点创建对应的配置实体类,该类中的属性(Property)对应节点中的特性(Attribute)和子元素(集合)。其编写步骤和开发方式与处理程序类似,只是这时我们的基类变成了 ConfigurationElement。
public class DataSystemElement : ConfigurationElement
{ private static readonly ConfigurationProperty _name = new ConfigurationProperty("name", typeof(string), null, null, new StringValidator(1), ConfigurationPropertyOptions.IsKey | ConfigurationPropertyOptions.IsRequired); private static readonly ConfigurationProperty _currentProvider = new ConfigurationProperty("currentProvider", typeof(string), string.Empty, ConfigurationPropertyOptions.IsRequired); private static readonly ConfigurationProperty _dataModules = new ConfigurationProperty("dataModules", typeof(DataModuleElementCollection), null, ConfigurationPropertyOptions.None); private static readonly ConfigurationProperty _dataProviders = new ConfigurationProperty(null, typeof(DataProviderElementCollection), null, ConfigurationPropertyOptions.IsRequired | ConfigurationPropertyOptions.IsDefaultCollection); private static ConfigurationPropertyCollection _properties = new ConfigurationPropertyCollection(); #region 类型构造函数 static DataSystemElement() { _properties.Add(_name); _properties.Add(_currentProvider); _properties.Add(_dataProviders); _properties.Add(_dataModules); } #endregion #region 构造函数 public DataSystemElement() { } public DataSystemElement(string name) { this.Name = name; } public DataSystemElement(string name, string currentProvider) { this.Name = name; this.CurrentProviderName = currentProvider; } #endregion #region 公共属性 public string Name { get { return (string)base[_name]; } set { base[_name] = value; } } public String CurrentProviderName { get { return (string)this[_currentProvider]; } set { this[_currentProvider] = value; } } public DataModuleElementCollection DataModules { get { return (DataModuleElementCollection)base[_dataModules]; } } public DataProviderElementCollection DataProviders { get { return (DataProviderElementCollection)base[_dataProviders]; } } #endregion}需要注意的是,<dataProvider> 和 <dataModules> 子元素处理方式的差异。
创建派生自 ConfigurationElementCollection 的类,在派生类中必须重写(override)的抽象方法:
对于非默认的节点集合类,还必须重写 CollectionType 和 ElementName 只读属性的getter,其代码可能如下:
protected override string ElementName
{ get { return "dataProvider"; //"dataProvider" replace with your elementName in collection. }}public override ConfigurationElementCollectionType CollectionType{ get { return ConfigurationElementCollectionType.BasicMap; }}可以重写 ThrowOnDuplicate 只读属性的getter,以指示当向 ConfigurationElementCollection 添加重复的 ConfigurationElement 是否会导致引发异常。默认情况,只有当该元素的 CollectionType 值为 AddRemoveClearMap 或 AddRemoveClearMapAlternate 时才会引发异常,如果希望非默认节点集合不接受重复项(通常如此),那么就必须重写该属性的getter,始终返回真(true)。
请注意,键和值都相同的元素不会被视为重复元素,而是接受此类元素且不出现提示,只有键相同而值不同的元素才被视为是重复元素。原因是这些元素不会进行竞争。但是,无法添加键相同而值不同的元素,因为无法从逻辑上确定哪个竞争值有效。
索引器的设计模式
通常需要定义索引器的两个重载(overloads),一个接受整型数下标的可读写的索引器;一个是接受字符串键值的只读索引器。
public DataProviderElement this[int index]
{ get { return (DataProviderElement)BaseGet(index); } set { if(BaseGet(index) != null) BaseRemoveAt(index); BaseAdd(index, value); }}public new DataProviderElement this[string name]{ get { return (DataProviderElement)BaseGet(name); }}
其他事项
通常还需要公开一些对集合操作的方法,大致如下:
public int IndexOf(DataProviderElement value)
{ return BaseIndexOf(value);}public void Add(DataProviderElement value){ BaseAdd(value);}public void Remove(DataProviderElement value){ if(BaseIndexOf(value) >= 0) BaseRemove(value.Name);}public void Remove(string name){ BaseRemove(name);}public void RemoveAt(int index){ BaseRemoveAt(index);}public void Clear(){ BaseClear();}这只是我想要撰写的有关 .NET 2.0 中的基础技术篇文章中的第一篇。在这篇文章中,我介绍了有关配置方面的一些基本概念,并阐述了在 .NET 2.0 中如何定制你自己的配置处理程序,以及这过程中需要注意的一些细节问题。您已经看到,通过对 .NET 2.0 中提供的基础架构的扩展,我们可以很容易完成特性化的配置定制。
您可以从以下网址之一选择下载本文附带的源码:
可能某些转载的格式不利于阅读,那么请从以下的原版位置之一查看本文: