解码.NET 2.0配置之谜(一)

引言

.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. 验证配置数据
    1. 使用预制验证器
    2. 回调验证
    3. 编写自己的验证器
  2. 保持类型安全
    1. 使用预制转换器
    2. 编写自己的转换器
  3. 关注性能
  4. 最佳配置实践
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之间。IntegerValidatorLongValidator都不支持这种验证,因此需要另外的解决方案。如果你仅仅需要在单个配置类中执行这种验证,最简单的解决方法是使用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、编写自己的验证器

编写自定义验证器是一个简单的任务,只要求类派生自ConfigurationValidatorBaseConfigurationValidatorBase类提供了必须重写的两种方法:

  • virtual bool CanValidate(Type type) - 确定是否可以验证该对象的类型。
  • abstract void Validate(object value) - 验证对象的值,如果验证失败抛出ArgumentException异常。

验证器不需要求特别的构造器,所以你可以自由地创建任意数量,任何参数,如果必要的话。值得注意的是,配置验证器可以创建和使用在自己的代码中,而不一定需要应用到ConfigurationProperty。这很方便,因为他允许你包装多个验证器到一个验证类中。因为对ConfigurationProperty只能应用一个验证器,包装验证器模式可以非常快速有用地应用多个验证器。

下面是一个自定义验证器的例子。这个验证器包装了StringValidatorRegexStringValidator。当编写自己的验证器时,你必须确保你重写了虚方法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
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值