引言
.NET的美妙特点之一是它的XML配置功能。在.NET 1.x时代,常见的应用程设置、数据库连接字符串、ASP.NET Web服务器配置和基本的自定义配置数据可以存储在.config文件中。自定义配置节可以使用一些基本自定义结构,允许少数几种信息存储在.config文件中。然而更复杂的配置,最常见的实现是自定义XML结构和自定义解析代码。尽管有多种不同性能的方法完成同样的事情,这种代码将变得相当复杂。
随着.NET 2.0,自己编写(可能很复杂、低性能、繁琐)代码来管理自定义XML配置结构的时代已经结束了。.NET 2.0内置的XML配置子系统自定义配置能力已经大大革新,拥有一些非常有用的和节省时间的功能。几乎任何XML配置结构你可能需要相对更少的工作且更容易。此外,反序列化.config中的XML总是可以重载的。这使得任何XML结构可以不失去.NET 2.0配置支持的其它高级功能。
该系列
这是该系列的第一篇文章。开始使用.NET 2.0所要求的所有核心概念和知识点都在这第一篇文章中提供。但是,.NET 2.0可用的配置框架是广泛的。从本文可以看到,它有许多功能和一些隐藏功能,可以填补框架的空白和疏漏。要继续学习有关.NET 2.0的配置框架,其它文章可以在以下链接找到:
- 揭开.NET 2.0配置之谜
- 解码.NET 2.0配置之谜
- 破解.NET 2.0配置之谜
写此系列的缘由(The mystery)
在略微超过一年的时间里我一直在使用.NET 2.0,可悲的是,我花了许多不眠之夜去修修补补自定义配置模型。自定义XML配置处理不断持续的性能下降,每个应用程序需要一个新的模型,这依赖于配置是如何存储的以及如何需要访问(从哪里)。直到8个月前左右,我用Reflector凿开.NET 2.0框架的一些程序集,我遇到一个奇妙的小类:System.Configuration.ConfigurationSection。挖掘更深一点,我发现为数众多的框架类都继承自ConfigurationSection和一些其它的类。自那时起,我花了几个不眠之夜吸收尽可能多的.NET 2.0的新配置特性。
我只想说,在互联网上关于自定义配置节的真正能力的资料非常少。通过Internet搜索、数小时的用Reflector研究框架代码、持续的实验和用自定义配置节,我终于知道了我需要知道的。这是生命的救星。最终,能轻松容易地创建和管理自定义XML配置、简单易用、易于管理并且性能优越……我将在与大家分享这个圣杯。我要求的唯一回报就是让任何人和你认识的用.config文件的人——他们应该更好地写.NET代码,看到这篇文章从而节省通过数个月的挖掘代码、互联网论坛和博客来学习它。
有关配置的主题
本文的目标是覆盖所有的.NET 2.0的配置,以及揭露一些更严密保护的秘密(标注:无正式文档的,无事实证明的…种种原因),可以节省你大量的时间和麻烦。首先,我们总览核心命名空间,它揭露了所有的自定义配置疯狂之处。然后,我们将进入具体执行和使用自定义配置。讨论的议题如下:
- 命名空间:System.Configuration
- 对象模型配置概念
- 编写一个基本的配置节
- 使用自定义配置节
- 添加自定义元素
- 添加元素集合
- 高级元素集合
- 自定义配置节组
- 保存配置更改
- 配置技巧和窍门
- 高级配置主题
- 附录
1.附录A:配置结构的级联
2.附录B:包含外部配置文件
1、命名空间:System.Configuration
新的.NET 2.0的配置精华核心是System.Configuration
命名空间。默认情况下,当引用了System.dll程序集,此命名空间是可用的。它包括.NET 1.1的所有功能,包括旧的ConfigurationSettings
类(现在.NET 2.0不推荐使用了)。然而,为了获得.NET 2.0的新特性,你必须添加对System.Configuration.dll程序集的引用。在这个程序集里你会发现新的配置系统的核心,ConfigurationManager静态类。
ConfigurationManager类是一个全局的访问一个应用程序的配置的入口。由于类是静态的,其所有的成员也是静态的。这使得读取如AppSettings
,ConnectionStrings和自定义配置节轻而易举。虽然这类似于
ConfigurationSettings
类它也提供一些能够更加安全地访问应用程序的配置的新功能。这些新功能,也可以允许将配置设置保存到任何配置节:自定义或其他的。
除了ConfigurationManager类是自定义配置节的生命线。下面列出的基类可以帮助你编写自己的配置对象模型。还将有更多关于这方面的。除了一个基类集是一个校验集,可以用来确保你的自定义配置节的准确性。此外,如果你的需要很简单,只需要用一些预制的配置。
基本类型
ConfigurationSection
- 配置节基类ConfigurationSectionCollection
- 配置节集合的基类ConfigurationSectionGroup
- 配置节组的基类ConfigurationSectionGroupCollection
- 配置节组集合的基类ConfigurationElement
- 配置元素的基类ConfigurationElementCollection
- 配置元素集合的基类ConfigurationConverterBase
- 自定义转换器基类*1ConfigurationValidatorBase
- 自定义验证器基类*2
支持类型
ConfigurationManager
- 提供对客户端应用程序配置文件的访问Configuration
- 表示一个应用程序的配置ConfigurationProperty
- 表示属性或配置元素的子元素ConfigurationPropertyAttribute
- 以声明方式指示.NET Framework,以实例化配置属性ConfigurationPropertyCollection
- 配置属性的集合ConfigurationPropertyOptions
- 指定要应用于属性的选项
验证类型
CallbackValidator
- 提供对对象的动态验证CallbackValidatorAttribute
- 指定用于代码验证的CallbackValidator对象IntegerValidator
- 对Int32值进行验证IntegerValidatorAttribute
- 以声明的方式指示.NET Framework对配置属性执行整数验证LongValidator
- 对Int64值进行验证。LongValidatorAttribute
- 以声明的方式指示.NET Framework对配置属性执行长整型验证PositiveTimeSpanValidator
- 对TimeSpan对象进行验证。PositiveTimeSpanValidatorAttribute
- 以声明的方式指示.NET Framework对配置属性执行时间验证RegexStringValidator
- 根据正则表达式提供的规则提供字符串验证RegexStringValidatorAttribute
- 以声明方式指示.NET Framework使用正则表达式在配置属性中执行字符串验证StringValidator
- 对字符串进行验证StringValidatorAttribute
- 以声明的方式指示.NET Framework对配置属性执行字符串验证SubclassTypeValidator
- 验证一个对象是否是指定类型的派生类*3SubclassTypeValidatorAttribute
- 以声明方式指示.NET Framework对配置属性执行验证TimeSpanValidator
- 对TimeSpan对象进行验证TimeSpanValidatorAttribute
- 以声明的方式指示.NET Framework对配置属性执行时间验证
转换器类型
CommaDelimitedStringCollectionConverter
-将以逗号分隔的字符串值和
CommaDelimitedStringCollection
对象相互转换GenericEnumConverter
- 在字符串和枚举类型之间进行转换InfiniteIntConverter
- 在字符串和标准无限或整数值之间转换InfiniteTimeSpanConverter
- 在字符串和标准无限TimeSpan值之间转换TimeSpanMinutesConverter
- 转换以分钟表示的时间跨度TimeSpanMinutesOrInfiniteConverter
- 转换以分钟表示(或作为标准的无限时间跨度)的TimeSpanTimeSpanSecondsConverter
- 转换以秒表示的时间跨度TimeSpanSecondsOrInfiniteConverter
- 转换以秒表示的TimeSpan,或将其转换为标准的无限时间跨度TypeNameConverter
- 在类型和字符串值之间转换WhiteSpaceTrimStringConverter
- 将字符串转换为它的规范化格式
预制的配置节
AppSettingsSection
- 为<appSettings>
配置节提供配置系统支持ConnectionStringsSection
- 提供对<connectionStrings>
配置节的编程访问ProtectedConfigurationSection
- 提供对configProtectedData配置节的编程访问IgnoreSection
- 为不是由System.Configuration类型处理的配置节提供包装类型定义
预制配置集合*4
CommaDelimitedStringCollection
- 与CommaDelimitedStringCollectionConverter结合使用(译注:表示以逗号分隔的字符串元素的集合)
KeyValueConfigurationCollection
- 用于在配置节中配置键/值对(译注:包含KeyValueConfigurationElement
对象的集合)NameValueConfigurationCollection
- 用于在配置节中配置名称/值对(译注:包含NameValueConfiguration
对象的集合)Element
注意:
- *1自定义转换器用于在XML文件中字符串表示与配置对象模型中强(译注:natively,或者翻译为“原生”?下同)类型之间转换
- *2自定义验证器是用来验证在配置对象模型强类型数据的准确性
- *3这关系到配置对象模型的概念,将在下一节讨论
- *4这些常见的配置集合可用于自定义配置节
2、对象模型配置概念
在我们接下来创建一些自定义配置之前,有必要先来学习一些对象模型配置的概念。.NET 2.0配置系统最终提供了一组对象表示配置设置的结构和强类型访问配置数据。这与在XML文件中存储和检索配置的更多通用的方法相反,它们通常需要通过DOM或读取流读取值,通过DOM或者写流将改变写回文件。在我私人写的更高级的配置系统中,包括一些缓存机制来加速读取和写配置值。创建一个高度可定制的配置文件,同时保持良好的性能一直是一个难点。
.NET 2.0的配置对象模型不在需要那些庞大的处理XML配置数据的方法。作为一个简单的例子,将ConnectionStrings
节对通用配置元素的配置管理整合进ConfigurationManager
对象,通过一个唯一的名字非常容易查找和访问一个指定的数据库连接串。这是因为有一个.NET 集合类,它列出连接字符串配置的对象。每一个连接字符串对象都被一个名字键标记。.NET类对XML元素的映射如下所示:
ConnectionStringsSection | <connectionStrings> |
ConnectionStringSettingsCollection | [implicitly created] |
ConnectionStringSettings | <add name="MyConnection" connectionString="blahblah"> |
访问MyConnetcion就如下面所示代码一样简单:
string myConnectionString =
ConfigurationManager.ConnectionStrings["MyConnection"].ConnectionString;
ConnectionStrings
非常简单,所以让我们来看一个经常使用,但不那么明显的.NET 2.0配置系统:<system.web>
配置组。你可能不知道它,但是但这一个复杂的配置节使用了一组类,用这组类我们可以创建自定义的配置节。让我们以class>emement关系的形式来看一下System.Web.Configuration
对象模型:
Class: |
Element: |
SystemWebSectionGroup |
<system.web> |
AuthenticationSection |
<authentication> |
AuthorizationSection |
<authorization> |
CustomErrorsSection |
<customErrors> |
CustomErrorsCollection |
[implicitly created] |
CustomError |
<error statusCode="404" redirect="..."> |
HttpModulesSection |
<httpModules> |
HttpModuleActionCollection |
[implicitly created] |
HttpModuleAction |
<add name="myModule" type="..."> |
HttpHandlersSection |
<httpHandlers> |
HttpHandlerActionCollection |
[implicitly created] |
HttpModuleActionCollection |
<add verb="*" type="..." path="..."> |
这只是
System.Web
ASP.NET配置节组提供的一套完整的配置节的一小部分。这些设置都可以通过一个精巧包装的对象模型访问,它归根于System.Web.Configuration.SystemWebSectionGroup
类。在事件的要求下,使用这个对象模型甚至可以将更新和保存回web.config文件,假设该代码有改变和保存的权限。
.NET 2.0配置系统用这个对象模型为我们处理解析、验证、安全和population(译注:关于 Population:这个词原型是Populate,有填充的含义。例如老外有时候表述给下拉列表增加列表项时,就说“Populate the list”。我想在文章里应该是指反序列话时往配置文件中写入配置元素吧)。除了编写自定义配置节,这是相当简单的,它完全去除了对XML的考虑,当在应用程序中使用你的配置时。不仅如此,在应用程序的任何地方都可以直接访问这个精美的包装、强类型、安全对象模型,而无需担心向注册表那样给自定义XML配置文件查找或存储文件路径。为不一致、不灵活、低性能的自定义配置管理器烦恼的时代一去不复返了。
3、编写一个基本的配置节
如果在这之前的东西吓到了你,别担心。编写代码来提供自定义配置节到你的应用程序非常简单。处理难看的XML、安全检查、类型转换等等绝大部分问题,已经被.NET 2.0框架中已有代码处理了。由于System.Configuration的一组基类集,要创建一个简单的配置节的实际代码量非常非常少。让我们开始创建一个含有一个字符串值、一个布尔值和一个时间跨度值的简单配置节。每一个配置节都必须继承
ConfigurationSection
基类,让我从以下内容开始:
#region Using Statements using System; using System.Configuration; #endregion namespace Examples.Configuration { /// <summary> /// An example configuration section class. /// </summary> public class ExampleSection: ConfigurationSection { #region Constructors static ExampleSection() { // Predefine properties here } #endregion // Declare static property fields here // Declare expose properties here } }
一旦你开始定义一个配置节类,你将必须定义有效的配置属性。一个配置属性,通过
ConfigurationProperty
类表示,描述了一个配置项,它将在你的配置节中可用。有两种方法定义配置属性,编程式(programmatic)和声明式(declarative)。这两种方法我个人都喜欢用,因为声明式方法有助于自描述的代码,编程式方法更严谨。这保证了只有你期望的确切的对象模型生成和支持,但是维护起来有些乏味,因为对一个配置属性两者都要更新。注:在这文章中,作为一个完整的例子我将使用这两种方法。
让我们开始往我们的示例配置节中填写代码。首先定义静态属性字段,然后在类的静态构造器中创建那些字段。最后通过编写C#属性暴露配置数据。自定义的配置节完整源码应该看起来像下面这样:
#region Using Statements using System; using System.Configuration; #endregion namespace Examples.Configuration { /// <summary> /// An example configuration section class. /// </summary> public class ExampleSection: ConfigurationSection { #region Constructors /// <summary> /// Predefines the valid properties and prepares /// the property collection. /// </summary> static ExampleSection() { // Predefine properties here s_propString = new ConfigurationProperty( "stringValue", typeof(string), null, ConfigurationPropertyOptions.IsRequired ); s_propBool = new ConfigurationProperty( "boolValue", typeof(bool), false, ConfigurationPropertyOptions.None ); s_propTimeSpan = new ConfigurationProperty( "timeSpanValue", typeof(TimeSpan), null, ConfigurationPropertyOptions.None ); s_properties = new ConfigurationPropertyCollection(); s_properties.Add(s_propString); s_properties.Add(s_propBool); s_properties.Add(s_propTimeSpan); } #endregion #region Static Fields private static ConfigurationProperty s_propString; private static ConfigurationProperty s_propBool; private static ConfigurationProperty s_propTimeSpan; private static ConfigurationPropertyCollection s_properties; #endregion #region Properties /// <summary> /// Gets the StringValue setting. /// </summary> [ConfigurationProperty("stringValue", IsRequired=true)] public string StringValue { get { return (string)base[s_propString]; } } /// <summary> /// Gets the BooleanValue setting. /// </summary> [ConfigurationProperty("boolValue")] public bool BooleanValue { get { return (bool)base[s_propBool]; } } /// <summary> /// Gets the TimeSpanValue setting. /// </summary> [ConfigurationProperty("timeSpanValue")] public TimeSpan TimeSpanValue { get { return (TimeSpan)base[s_propTimeSpan]; } } /// <summary> /// Override the Properties collection and return our custom one. /// </summary> protected override ConfigurationPropertyCollection Properties { get { return s_properties; } } #endregion } }
一旦你完成了,就是这样。此自定义配置节准备就绪。如果你喜欢写更少的代码,你可以不用构造器和静态字段,将每个值用过一个字符串键存储在默认的属性集合中。这将导致配置节本身要求更少的代码,但是要求整理上更少的定义。上述属性将被下面的代码的替代,静态字段和构造器可以删掉。顺便说一下,这就是纯粹的声明式方法:
#region Properties /// <summary> /// Gets the StringValue setting. /// </summary> [ConfigurationProperty("stringValue", IsRequired=true)] public string StringValue { get { return (string)base["stringValue"]; } } /// <summary> /// Gets the BooleanValue setting. /// </summary> [ConfigurationProperty("boolValue")] public bool BooleanValue { get { return (bool)base["boolValue"]; } } /// <summary> /// Gets the TimeSpanValue setting. /// </summary> [ConfigurationProperty("timeSpanValue")] public TimeSpan TimeSpanValue { get { return (TimeSpan)base["timeSpanValue"]; } } #endregion
一个快速笔记,ConfigurationProperty
真正是什么。默认,除非一个自定义元素明确地写出,所有的ConfigurationProperty
都定义在一个App.config或Web.config文件中。创建和自定义配置元素集合将在后面讨论。
4、使用自定义配置节
现在你已经为你的自定义配置写了一个类,则你需要在一个App.config文件中定义自定义节。你必须添加必要的XML,以便他能被.NET 2.0配置系统解析。App.config文件要改成下面的那样,ExampleSection才可用,假定上述代码已经被编译成Examples.Configuration.dll:
<configuration> <configSections> <section name="example" type="Examples.Configuration.ExampleSection, Examples.Configuration" /> </configSections> <example stringValue="A sample string value." boolValue="true" timeSpanValue="5:00:00" /> </configuration>
<configSections>及其子节点的一个快速解释。与内置的配置节不一样,他们是隐式定义的,自定义配置节必须显式地定义。这是通过
<configSections>
元素和它相应的<section>
子元素来完成的。应当指出,一个节的名字一般是很重要的,不能任意选择的。一会儿你就知道为什么了。一个配置节映射到一个完全限定的类名,其后是程序集名。选择性地,你可以指定区域性、版本、公钥(用于程序集的签名)值,如果你想确保只在特定的程序集的特定版本中搜索,当你的.config被解析时。在上面的例子中,字符串逗号之前的那部分是类名,后面那部分是程序集名(不包括.dll后缀)。
最后,你可以在代码中用
ConfigurationManager
类使用你的自定义配置了。ConfigurationManager
提供了一个叫做GetSection
的方法,它允许你访问任何已经定义了的自定义设置。当你访问自定义配置节是,通常最好用动态转换和检查是否为空,就像这样:
private string m_string; private bool m_bool; private TimeSpan m_timespan; void GetExampleSettings() { ExampleSection section = ConfigurationManager.GetSection("example") as ExampleSection; if (section != null) { m_string = section.StringValue; m_bool = section.BooleanValue; m_timespan = section.TimeSpanValue; } }
这里最重要注意的是选择代表一个配置节根元素的名字。在App.config文件的例子中,节被定义为 “example”。因为调用
GetSection()
时加载节查找名字‘example’,在App.config中任何试图为“example“重命名为其他名字将导致GetExampleSettings()
失败。这是不可以任意选择一个自定义配置节的名子,除非明确地设计调整以保持一致,可能是通过使用另一个配置节。