引言
.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配置框架提供什么、不知道如何编写和使用自定义ConfigurationSection
类,建议你继续阅读之前,阅读该系列的第一部分。
本文的配置主题
- 验证配置数据
- 使用预制验证器
- 回调验证
- 编写自己的验证器
- 保持类型安全
- 使用预制转换器
- 编写自己的转换器
- 关注性能
- 最佳配置实践
1、验证配置数据
正如我们在“揭开.NET 2.0配置之谜”中所知道的,编写自定义配置节是非常简单明了的。写一个.NET 2.0配置节的好处是有效、全局访问、类型安全、并验证配置。类型安全和验证时两个主题在上篇文章中简短地讨论了,但从未详细阐述。配置这两个方面,而并非总是关键,但对应用程序的健康和正确性非常重要,当你期望具体访问和数据类型。
验证你自己的配置数据的正确性是非常简单的。.NET 2.0的配置框架实际上包括几个预制的验证器,应该能满足大多数验证的需要。如果一个预制的验证器是不够的,有两个其它的方法验证你的配置数据。值得注意的是,一次只能一个验证器应用于配置属性(property)。有时,你可能会发现自己需要两个验证器,最简单的解决方案是创建自己的包装验证器,在自定义的验证器类中使用两个预制的验证器。
1.1、使用预制验证器
在自定义配置类中使用预制验证器,应用于你想验证的属性,是一件简单的事情。有两种方法应用验证器,命令式(imperative)和声明式(declarative)。如果你选择明确地创建配置属性(properties)和覆写你的配置节和配置元素的属性(properties)集合,你必须使用命令式方法。否则,你可以声明应用一个验证器对每个属性(properties)使用属性(attributes)(译注——原文:Otherwise, you may declaratively apply a validator to each property using attributes. )。你将会看到下面的列表,所有的配置验证器类有一个匹配的属性(attribute)类:
验证类型
CallbackValidator
- 允许动态验证一个属性值
CallbackValidatorAttribute
- 属性类提供声明地应用回调验证器(译注:属性类提供CallbackValidator
对象和要验证的代码之间的关联) IntegerValidator
- 允许验证一个整数(Int32)配置值 IntegerValidatorAttribute
- 属性类提供声明地应用整数(Int32)验证器 LongValidator
- 允许验证一个长整数(Int64)配置值 LongValidatorAttribute
- 属性类提供声明地应用长整数(Int64)验证器 PositiveTimeSpanValidator
- 允许验证a postive time span配置值 PositiveTimeSpanValidatorAttribute
- 属性类提供声明地应用post time span验证器 RegexStringValidator
- 允许用正则表达式验证一个字符串 RegexStringValidatorAttribute
- 属性类提供声明地应用正则验证器 StringValidator
- 允许验证一个字符串配置值 StringValidatorAttribute
- 属性类提供声明地应用字符串验证器 SubclassTypeValidator
- 允许验证一个类型是否继承自指定类型 SubclassTypeValidatorAttribute
- 属性类提供声明地应用子类类型验证器 TimeSpanValidator
- 允许验证一个时间跨度配置值 TimeSpanValidatorAttribute
- 属性类提供声明地应用时间跨度验证器
在大多数情况下,这些验证不言而明。验证ints、longs和Timespan是相当简单的事,不需要用例子演示,但他们做了一个简单的介绍。这些验证器有许多额外的功能值得讨论。假设我们有一个类似下面的配置节:
代码<configuration>
<configSections>
<section name="example"
type="Examples.Configuration.ValidatedExampleSection, Examples" />
</configSections>
<example myTimeSpan="8:15:00" myInt="10" myLong="6018427387904" />
</configuration>
我们可以写一个验证配置节,确保三个属性(properties)数据是有效的Timespan、int、long数据,落在我们希望的范围内。如果在序列化或反序列化中数据不正确,将会抛出一个ArgumentException
异常。(注:不应用于属性验证,引发的一场可能是少数的集中,包括ConfigurationErrorsException
, NullReferenceException
, or InvalidCastException
.通过使用验证器,我们知道验证配置数据时查找哪些异常。)特别需要注意,用我们的验证器可以限制值的范围。我们的配置节应该是这个样子:
代码public class ValidatedExampleSection: ConfigurationSection
{
#region Constructor
static ValidatedExampleSection()
{
s_propMyTimeSpan = new ConfigurationProperty(
"myTimeSpan",
typeof(TimeSpan),
TimeSpan.Zero,
null,
new TimeSpanValidator(TimeSpan.Zero, TimeSpan.FromHours(24)),
ConfigurationPropertyOptions.IsRequired
);
s_propMyInt = new ConfigurationProperty(
"myInt",
typeof(int),
0,
null,
new IntegerValidator(-10, 10),
ConfigurationPropertyOptions.IsRequired
);
s_propMyLong = new ConfigurationProperty(
"myLong",
typeof(long),
0,
null,
new LongValidator(Int64.MinValue, Int64.MaxValue),
ConfigurationPropertyOptions.IsRequired
);
s_properties = new ConfigurationPropertyCollection();
s_properties.Add(s_propMyTimeSpan);
s_properties.Add(s_propMyInt);
s_properties.Add(s_propMyLong);
}
#endregion
#region Fields
private static ConfigurationPropertyCollection s_properties;
private static ConfigurationProperty s_propMyTimeSpan;
private static ConfigurationProperty s_propMyInt;
private static ConfigurationProperty s_propMyLong;
#endregion
#region Properties
[ConfigurationProperty("myTimeSpan", DefaultValue=TimeSpan.Zero,
IsRequired=true)]
[TimeSpanValidator(MinValueString="0:0:0", MaxValueString="24:0:0")]
public TimeSpan MyTimeSpan
{
get { return (TimeSpan)base[s_propMyTimeSpan]; }
}
[ConfigurationProperty("myInt", DefaultValue=0, IsRequired=true)]
[IntegerValidator(-10, 10)]
public int MyInt
{
get { return (int)base[s_propMyInt]; }
}
[ConfigurationProperty("myLong", DefaultValue=0, IsRequired=true)]
[LongValidator(Int64.MinValue, Int64.MaxValue)]
public int MyInt
{
get { return (int)base[s_propMyLong]; }
}
#endregion
}
如果任何配置数据在指定的范围外(i.e.,myInt是-12),当调用ConfigurationManager.GetSection()
时,你可以捕捉一个ArgumentException
异常,并采取适当的行动。除了能够检查在指定范围内的值,也可以指定在范围之外。在这种情况下,任何在指定范围外的值是有效的,并在指定范围内的任何值是无效的,导致引发异常。此外,期望这些验证器检查特定的单一值是可以的,如果设置了完整构造器的决议参数。验证器new TimeSpanValidator(TimeSpan.MinValue, TimeSpan.MaxValue, false, 15)
要求时间跨度正好等于15秒。
其他预制验证器未必一定像刚才讨论的那么简单。StringValidator
,事实证明,不验证一个特定的字符串是怎么配置的。相反,它允许验证字符串的长度,确保它在最小和最大值之间。StringValidator
也允许指定一个字符串的无效字符,并抛出异常,如果字符串包含任何那些字符。如果你希望验证一个配置字符串符合某些格式要求,RegexStringValidator
实际上是你所需要的。有了这些验证器,你能指定任何标准正则表达式,针对实际配置的字符串进行匹配。没有预制的验证器,即能验证字符串的长度又能匹配一个正则表达式。
这部分我们讨论的最后一个预制验证器是SubclassTypeValidator
。这个方便的小验证器允许你验证特定配置属性对象类型是否继承自指定类型。当用一个自定类型序列化和从一个自定义类反序列化数据,出其他外,这将非常有用。这个验证器更详细的介绍将在编写自己的转换器部分进行。
1.2、回调验证
大多数时候,当编写任何一段代码,一个简单的解决方法是所有初始需求。倘若你发现自己写一个验证配置节及一个预制验证器是不够的,但你的要求不是特别复杂,CallbackValidator
通常就足够了。这个方便的小验证器用一个CallbackValidator
委托作为参数,它应指向一个方法,它用一个单个对象作为参数。你可以执行任何的验证,他们包含在这个回调函数中。
假设我们有一个配置节,期望整数值模10后在-100到100之间。IntegerValidator
和 LongValidator
都不支持这种验证,因此需要另外的解决方案。如果你仅仅需要在单个配置类中执行这种验证,最简单的解决方法是使用CallbackValidator
。
代码public class ValidatedExampleSection: ConfigurationSection
{
#region Constructor
static ValidatedExampleSection()
{
s_propMyInt = new ConfigurationProperty(
"myInt",
typeof(int),
0,
null,
new CallbackValidator(new
ValidatorCallback(ModRangeValidatorCallback)),
ConfigurationPropertyOptions.IsRequired
);
s_properties = new ConfigurationPropertyCollection();
s_properties.Add(s_propMyInt);
}
#endregion
#region Fields
private static ConfigurationPropertyCollection s_properties;
private static ConfigurationProperty s_propMyInt;
#endregion
#region Properties
[ConfigurationProperty("myInt", DefaultValue=0, IsRequired=true)]
[CallbackValidator("Examples.Configuration.
ValidatedExampleSection.ModRangeValidatorCallback")]
public int MyInt
{
get { return (int)base[s_propMyInt]; }
}
#endregion
#region Helpers
private void ModRangeValidatorCallback(object value)
{
int intVal = (int)value;
if (intVal >= -100 && intVal <= 100)
{
if (intVal % 10 != 0)
throw new ArgumentException("The integer " +
"value is not a multiple of 10.");
}
else
{
throw new ArgumentException("The integer value is not" +
" within the range -100 to 100");
}
}
#endregion
}
可以在一个配置节或配置元素中对多个属性(properties)重复使用一个回调。如果需要穿越多个配置类使用回调,你可以创建一个容器类且将你所有的回调组到单个位置。但是,如果你需要对多个属性或属性类重用一个验证器,较好的解决办法是编写一个自定义配置验证器类及其相应的属性。这将允许你更好地封装和共享代码,以更少的混淆方式。
1.3、编写自己的验证器
编写自定义验证器是一个简单的任务,只要求类派生自ConfigurationValidatorBase
。ConfigurationValidatorBase
类提供了必须重写的两种方法:
virtual bool CanValidate(Type type) - 确定是否可以验证该对象的类型。
abstract void Validate(object value)
- 验证对象的值,如果验证失败抛出ArgumentException
异常。
验证器不需要求特别的构造器,所以你可以自由地创建任意数量,任何参数,如果必要的话。值得注意的是,配置验证器可以创建和使用在自己的代码中,而不一定需要应用到ConfigurationProperty
。这很方便,因为他允许你包装多个验证器到一个验证类中。因为对ConfigurationProperty
只能应用一个验证器,包装验证器模式可以非常快速有用地应用多个验证器。
下面是一个自定义验证器的例子。这个验证器包装了StringValidator
和RegexStringValidator
。当编写自己的验证器时,你必须确保你重写了虚方法CanValidate。尽管他不是抽象的,但它的返回值为false,在序列化调用时,这将导致你的验证总是失败的。
代码public class RegexStringWrapperValidator: ConfigurationValidatorBase
{
#region Constructors
public RegexStringWrapperValidator(string regex) :
this (regex, 0, 0x7fffffff)
{
}
public RegexStringWrapperValidator(string regex, int minLength) :
this(regex, minLength, 0x7fffffff)
{
}
public RegexStringWrapperValidator(string regex, int minLength,
int maxLength)
{
m_regexValidator = new RegexStringValidator(regex);
m_stringValidator = new StringValidator(minLength, maxLength);
}
#endregion
#region Fields
private RegexStringValidator m_regexValidator;
private StringValidator m_stringValidator;
#endregion
#region Overrides
public override bool CanValidate(Type type)
{
return (type == typeof(string));
}
public override void Validate(object value)
{
m_stringValidator.Validate(value);
m_regexValidator.Validate(value);
}
#endregion
}
正如你所看到的,编写自定义验证其实非常简单的。然而,我们还没有做得比较好。我们仍然需要编写自定义属性,以便我们能够声明地对一个ConfigurationProperty
应用我们的验证器。此步骤只对你有用,如果你喜欢喜欢声明地编程,且没有必要,如果你按照第一篇文章中建议的命令式方法。
代码public sealed class RegexStringWrapperValidatorAttribute:
ConfigurationValidatorAttribute
{
#region Constructors
public RegexStringWrapperValidatorAttribute(string regex)
{
m_regex = regex;
m_minLength = 0;
m_maxLength = 0x7fffffff;
}
#endregion
#region Fields
private string m_regex;
private int m_minLength;
private int m_maxLength;
#endregion
#region Properties
public string Regex
{
get { return m_regex; }
}
public int MinLength
{
get
{
return m_minLength;
}
set
{
if (m_maxLength < value)
{
throw new ArgumentOutOfRangeException("value",
"The upper range limit value must be greater" +
" than the lower range limit value.");
}
m_minLength = value;
}
}
public int MaxLength
{
get
{
return m_maxLength;
}
set
{
if (m_minLength > value)
{
throw new ArgumentOutOfRangeException("value",
"The upper range limit value must be greater " +
"than the lower range limit value.");
}
m_maxLength = value;
}
}
public override ConfigurationValidatorBase ValidatorInstance
{
return new RegexStringWrapperValidator(m_regex, m_minLength,
m_maxLength);
}
#endregion
}
未完待续……请继续关注!如果您觉得好,推荐下能让更多的人看到。
声明:此文是译文,实因水平有限,若有翻译不当之处请不吝指出,以免此译文误导他人,在此谢过!
英文原文:Jon Rista,Decoding the Mysteries of .NET 2.0 Configuration