技术开发 频道

揭开.NET 2.0配置隐藏的秘密

  【IT168技术引言

  .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 - 自定义转换器基类*1

  ConfigurationValidatorBase - 自定义验证器基类*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 - 验证一个对象是否是指定类型的派生类*3

  SubclassTypeValidatorAttribute - 以声明方式指示.NET Framework对配置属性执行验证

  TimeSpanValidator - 对TimeSpan对象进行验证

  TimeSpanValidatorAttribute - 以声明的方式指示.NET Framework对配置属性执行时间验证

  转换器类型

  CommaDelimitedStringCollectionConverter - 将以逗号分隔的字符串值和CommaDelimitedStringCollection对象相互转换

  GenericEnumConverter - 在字符串和枚举类型之间进行转换

  InfiniteIntConverter - 在字符串和标准无限或整数值之间转换

  InfiniteTimeSpanConverter - 在字符串和标准无限TimeSpan值之间转换

  TimeSpanMinutesConverter - 转换以分钟表示的时间跨度

  TimeSpanMinutesOrInfiniteConverter - 转换以分钟表示(或作为标准的无限时间跨度)的TimeSpan

  TimeSpanSecondsConverter - 转换以秒表示的时间跨度

  TimeSpanSecondsOrInfiniteConverter - 转换以秒表示的TimeSpan,或将其转换为标准的无限时间跨度

  TypeNameConverter - 在类型和字符串值之间转换

  WhiteSpaceTrimStringConverter - 将字符串转换为它的规范化格式

  预制的配置节

  AppSettingsSection - 为 配置节提供配置系统支持

  ConnectionStringsSection - 提供对配置节的编程访问

  ProtectedConfigurationSection - 提供对configProtectedData配置节的编程访问

  IgnoreSection - 为不是由System.Configuration类型处理的配置节提供包装类型定义

  预制配置集合*4

  CommaDelimitedStringCollection - 与CommaDelimitedStringCollectionConverter结合使用(译注:表示以逗号分隔的字符串元素的集合)

  KeyValueConfigurationCollection - 用于在配置节中配置键/值对(译注:包含KeyValueConfigurationElement对象的集合)

  NameValueConfigurationCollection - 用于在配置节中配置名称/值对(译注:包含NameValueConfigurationElement对象的集合)

  注意:

  *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
    }
}

    

  一旦你完成了,就是这样。此自定义配置节准备就绪。如果你喜欢写更少的代码,你可以不用构造器和静态字段,将每个值用过一个字符串键存储在默认的属性集合中。这将导致配置节本身要求更少的代码,但是要求整理上更少的定义。上述属性将被下面的代码的替代,静态字段和构造器可以删掉。顺便说一下,这就是纯粹的声明式方法:

  一个快速笔记,ConfigurationProperty真正是什么。默认,除非一个自定义元素明确地写出,所有的

    ConfigurationProperty都定义在一个App.config或Web.config文件中。创建和自定义配置元素集合将在后面讨论。

#region Properties

  
///

  
/// Gets the StringValue setting.

  
///

  [ConfigurationProperty(
"stringValue", IsRequired=true)]

  
public string StringValue

  {

  
get { return (string)base["stringValue"]; }

  }

  
///

  
/// Gets the BooleanValue setting.

  
///

  [ConfigurationProperty(
"boolValue")]

  
public bool BooleanValue

  {

  
get { return (bool)base["boolValue"]; }

  }

  
///

  
/// Gets the TimeSpanValue setting.

  
///

  [ConfigurationProperty(
"timeSpanValue")]

  
public TimeSpan TimeSpanValue

  {

  
get { return (TimeSpan)base["timeSpanValue"]; }

  }

  #endregion

  4、使用自定义配置节

  现在你已经为你的自定义配置写了一个类,则你需要在一个App.config文件中定义自定义节。你必须添加必要的XML,以便他能被.NET 2.0配置系统解析。App.config文件要改成下面的那样,ExampleSection才可用,假定上述代码已经被编译成Examples.Configuration.dll:


    stringValue
="A sample string value."

  boolValue
="true"

  timeSpanValue
="5:00:00"

  
/>

 

  及其子节点的一个快速解释。与内置的配置节不一样,他们是隐式定义的,自定义配置节必须显式地定义。这是通过元素和它相应的

子元素来完成的。应当指出,一个节的名字一般是很重要的,不能任意选择的。一会儿你就知道为什么了。一个配置节映射到一个完全限定的类名,其后是程序集名。选择性地,你可以指定区域性、版本、公钥(用于程序集的签名)值,如果你想确保只在特定的程序集的特定版本中搜索,当你的.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()失败。这是不可以任意选择一个自定义配置节的名子,除非明确地设计调整以保持一致,可能是通过使用另一个配置节。

  5、添加自定义元素

  默认情况下,自定义配置节中所有的配置属性(properties)在.config文件中被表示成属性(attributes)。这并不总是必然的,然而,一个更复杂XML结构,由属性(attributes)和元素(elements)混合组成的,是需要的。不要怕:.NET的配置系统完全支持自定义配置元素,他们的属性(properties)可以是属性(attributes)或者多个嵌套元素。要创建自定义配置元素,简单地写一个类继承自ConfigurationElement而不是ConfigurationSection。配置元素的执行细节跟配置节一样,不同之处是元素必须嵌套在配置节中。

  让我们继续,嵌套一个自定义元素在ExampleSection中。让我们这个嵌套元素存储一个DateTime值和一个整数值。创建这个类的代码如下所示:

using System;

  
using System.Configuration;

  #endregion

  
namespace Examples.Configuration

  {

  
///

  
/// An example configuration element class.

  
///

  
public class NestedElement: ConfigurationElement

  {

  
#region Constructors

  
///

  
/// Predefines the valid properties and prepares

  
/// the property collection.

  
///

  
static NestedElement()

  {

  
// Predefine properties here

  s_propDateTime
= new ConfigurationProperty(

  
"dateTimeValue",

  
typeof(DateTime),

  null,

  ConfigurationPropertyOptions.IsRequired

  );

  s_propInteger
= new ConfigurationProperty(

  
"integerValue",

  
typeof(int),

  
0,

  ConfigurationPropertyOptions.IsRequired

  );

  s_properties
= new ConfigurationPropertyCollection();

  s_properties.Add(s_propDateTime);

  s_properties.Add(s_propInteger);

  }

  #endregion

  
#region Static Fields

  
private static ConfigurationProperty s_propDateTime;

  
private static ConfigurationProperty s_propInteger;

  
private static ConfigurationPropertyCollection s_properties;

  #endregion

  
#region Properties

  
///

  
/// Gets the DateTimeValue setting.

  
///

  [ConfigurationProperty(
"dateTimeValue", IsRequired=true)]

  
public DateTime StringValue

  {

  
get { return (DateTime)base[s_propDateTime]; }

  }

  
///

  
/// Gets the IntegerValue setting.

  
///

  [ConfigurationProperty(
"integerValue")]

  
public int IntegerValue

  {

  
get { return (int)base[s_propInteger]; }

  }

  
///

  
/// Override the Properties collection and return our custom one.

  
///

  
protected override ConfigurationPropertyCollection Properties

  {

  
get { return s_properties; }

  }

  #endregion

  }

 

  自定义配置元素类#region Using Statements

  将这个元素加到我们之前创建的ExampleSection中,就像定义一个新属性一样简单。下面展示了添加一个嵌套元素的必要代码:

代码 public class ExampleSection: ConfigurationSection

  {

  
#region Constructors

  
///

  
/// Predefines the valid properties and prepares

  
/// the property collection.

  
///

  
static ExampleSection()

  {

  
// Create other properties...

  s_propElement
= new ConfigurationProperty(

  
"nestedElement",

  
typeof(NestedElement),

  null,

  ConfigurationPropertyOptions.IsRequired

  );

  s_properties
= new ConfigurationPropertyCollection();

  
// Add other properties...

  s_properties.Add(s_propElement);

  }

  #endregion

  
#region Static Fields

  
private static ConfigurationProperty s_propElement;

  
// Other static fields...

  #endregion

  
#region Properties

  
// ...

  
///

  
/// Gets the NestedElement element.

  
///

  [ConfigurationProperty(
"nestedElement")]

  
public NestedElement Nested

  {

  
get { return (NestedElement)base[s_propElement]; }

  }

  
// ...

  #endregion

  }

    最后,在我们的XML配置文件中使用这个元素只需要简单地在标记中添加标记。值得注意的是,只能有一个nestedElement实例在example中。这种方式创建的嵌套元素,不允许有类似命名的元素集合。它允许一个特定元素的单个实例,在自定义节的一个特定的嵌套深度。下一节将讲在一个配置节中定义元素集合。完整的App.config文件应该像下面这样:

<configuration>
  
<configSections>
    
<section name="example" type="Examples.Configuration.ExampleSection,
                                  Examples.Configuration" />
  </configSections>

  
<example
    stringValue
="A sample string value."
    boolValue
="true"
    timeSpanValue
="5:00:00"
  
>
    
<nestedElement
      dateTimeValue
="10/16/2006"
      integerValue
="1"
    
/>
  
</example>
</configuration>

            使用新的套元素又是非常的简单,因为Nested属性(property)将暴露给我们的前面例子中使用的节变量:

private string m_string;
private bool m_bool;
private TimeSpan m_timespan;
private DateTime m_datetime;
private int m_int;

void GetExampleSettings()
{
    ExampleSection section
= ConfigurationManager.GetSection("example")
                            
as ExampleSection;
    
if (section != null)
    {
        m_string
= section.StringValue;
        m_bool
= section.BooleanValue;
        m_timespan
= section.TimeSpanValue;
        m_datetime
= section.Nested.DateTimeValue;
        m_int
= section.Nested.IntegerValue;
    }
}

  

  每个配置节可以由任意数量的属性(attributes)和元素(elements)组成,元素可以嵌套的任意的深度以满足应用程序。相对于其他XML用法,这总是一个好主意,符合相同的XML自定义配置节的非常好的做法。作为一般规则,数据集或大量的信息不应该保存在自定义配置节。我们将在高级配置主题那节讨论原因。这些“配置”部分,应该用于存储结构化的应用配置信息。

  6、添加元素集合

  在上一节,我们在一个配置节元素中创建一个嵌套元素。嵌套的元素仅限于只有一个实例,且必须出现在指定的元素中。创建元素集合或元素列表需要不同的和稍微复杂的方法。在配置文件中要创建一个配置元素集合或列表,你必须要创建一个类继承自ConfigurationElementCollection。几种集合可以被创建,两个主要集合类型是BasicMap和AddRemoveClearMap。

  任何用过配置节的人都会熟悉AddRemoveClearMap的集合类型。AddRemoveClearMap是ASP.NET web.config文件中的一个级联集合。级联集合允许元素在web站点路径级添加,移除或清除在低级别的应用程序级。此外,在低级别添加的任何新的唯一元素都将和高级别的所有元素合并。请参阅附录A,更多细节关于配置如何级联作用。Basic map更为严格,但允许其他不是“add”名称的元素添加到一个集合中。一个Basic map的别的元素名称的例子是System.Web的节,它支持一个元素的集合。

  由于AddRemoveClearMap集合是默认类型,让我们创建一个并将它加到我们之前的配置节例子中。创建一个元素集合的代码比配置节或单个元素要稍微复杂一些,但是整体来说仍然非常简单。下面的元素集合代码遵循一个标准模式,.NET 2.0框架中大部分元素集合都是它:

[ConfigurationCollection(typeof(ThingElement),
    CollectionType
=ConfigurationElementCollectionType.AddRemoveClearMap)]
public class ExampleThingElementCollection: ConfigurationElementCollection
{
    
#region Constructors
    
static ExampleThingElementCollection()
    {
        m_properties
= new ConfigurationPropertyCollection();
    }

    
public ExampleThingElementCollection()
    {
    }
    #endregion

    
#region Fields
    
private static ConfigurationPropertyCollection m_properties;
    #endregion

    
#region Properties
    
protected override ConfigurationPropertyCollection Properties
    {
        
get { return m_properties; }
    }
    
    
public override ConfigurationElementCollectionType CollectionType
    {
        
get { return ConfigurationElementCollectionType.AddRemoveClearMap; }
    }
    #endregion

    
#region Indexers
    
public ThingElement this[int index]
    {
        
get { return (ThingElement)base.BaseGet(index); }
        
set
        {
            
if (base.BaseGet(index) != null)
            {
                base.BaseRemoveAt(index);
            }
            base.BaseAdd(index, value);
        }
    }

    
public ThingElement this[string name]
    {
        
get { return (ThingElement)base.BaseGet(name); }
    }
    #endregion
    
    
#region Overrides
    
protected override ConfigurationElement CreateNewElement()
    {
        
return new ThingElement();
    }

    
protected override object GetElementKey(ConfigurationElement element)
    {
        
return (element as ThingElement).Name;
    }
    #endregion
}

  一般都需要提供一个通过数字索引的索引器。通过一个元素的关键字索引的索引器也是非常方便的。在这个例子中,关键字是一个字符串名称。两个重写方法CreateNewElement和GetElementKey,对于确保你的集合功能正常非常重要。CreateNewElement有两个重载的方法,一个没有参数,另一个以一个元素名作参数,如果你重载了默认的AddRemoveClearMap行为。更多的关于这个将在高级主题那节讨论。默认,CreateNewElement(string elementName)重载调用CreateNewElement(),所以他并非总是需要重载的。GetElementKey返回指定的配置元素的值,而且返回值唯一标识他。在我们的例子中,关键字是Name属性(property),将在我们的ThingElement定义。最后,你可能已经注意到属性(Properties)集合被重写了。这个原因不是很明显,除非你深入研究.NET框架的源代码,但是可以说这是一个性能优化。

  我们的集合并没有完全完成,我们需要收集一些东西。对于我们的例子,就是ThingElement。这是另外一个ConfigurationElement类,类似于我们的之前的NestedElement。

public class ThingElement: ConfigurationElement
{
        
#region Constructors
        
/// <summary>
        
/// Predefines the valid properties and prepares
        
/// the property collection.
        
/// </summary>
        
static ThingElement()
        {
            
// Predefine properties here
            s_propName
= new ConfigurationProperty(
                
"name",
                
typeof(string),
                null,
                ConfigurationPropertyOptions.IsRequired
            );

            s_propType
= new ConfigurationProperty(
                
"type",
                
typeof(string),
                
"Normal",
                ConfigurationPropertyOptions.None
            );

            s_propColor
= new ConfigurationProperty(
                
"color",
                
typeof(string),
                
"Green",
                ConfigurationPropertyOptions.None
            );

            s_properties
= new ConfigurationPropertyCollection();
            
            s_properties.Add(s_propName);
            s_properties.Add(s_propType);
            s_properties.Add(s_propColor);
        }
        #endregion

        
#region Static Fields
        
private static ConfigurationProperty s_propName;
        
private static ConfigurationProperty s_propType;
        
private static ConfigurationProperty s_propColor;

        
private static ConfigurationPropertyCollection s_properties;
        #endregion

        
        
#region Properties
        
/// <summary>
        
/// Gets the Name setting.
        
/// </summary>
        [ConfigurationProperty(
"name", IsRequired=true)]
        
public string Name
        {
            
get { return (string)base[s_propName]; }
        }

        
/// <summary>
        
/// Gets the Type setting.
        
/// </summary>
        [ConfigurationProperty(
"type")]
        
public string Type
        {
            
get { return (string)base[s_propType]; }
        }

        
/// <summary>
        
/// Gets the Type setting.
        
/// </summary>
        [ConfigurationProperty(
"color")]
        
public string Color
        {
            
get { return (string)base[s_propColor]; }
        }

        
/// <summary>
        
/// Override the Properties collection and return our custom one.
        
/// </summary>
        
protected override ConfigurationPropertyCollection Properties
        {
            
get { return s_properties; }
        }
        #endregion
}

  我们的ThingElement很简单,只提供了一个名称、类型和颜色。现在你应该注意到,至此我们的配置类中我们已经重写了Properties集合。这不是一个不要的步骤,但它可以提高配置节的性能和效率。更多详细介绍在高级主题那节。我们可以通过添加另一个ConfigurationProperty使这个集合在我们的ExampleSection中可以访问,实现方法和NestedElement一样。只是这次,用ExampleThingElementCollection替换了NestedElement。用“Thing”命名元素,现在由我们的代码支持的例子如下:

<configuration>
  
<configSections>
    
<section name="example" type="Examples.Configuration.ExampleSection,
                                     Examples.Configuration" />
  </configSections>

  
<example
    stringValue
="A sample string value."
    boolValue
="true"
    timeSpanValue
="5:00:00"
  
>
    
<nestedElement
      dateTimeValue
="10/16/2006"
      integerValue
="1"
    
/>
    
<things>
      
<add name="slimy" type="goo" />
      
<add name="metal" type="metal" color="silver" />
      
<add name="block" type="wood" color="tan" />
    
</things>
  
</example>
</configuration>

       7、高级元素集合

         在上一节中,您学习了如何创建一个标准的AddRemoveClearMap集合类型,它也是默认的类型。总共有四个类型的集合(有两种类型,每种类型有两种版本):AddRemoveClearMapAddRemoveClearMapAlternate以及BasicMapBasicMapAlternate。从上一节,我们知道AddRemoveClearMap是如何其作用的。BasicMap 限制比AddRemoveClearMap 强,它不允许较低级的web.config修改从较高级的web.config继承的任何东西,但它允许除<add>名称之外的元素。两种主要类型的替换版本只是对元素的排序不同,添加继承的元素,将它们他在最后。

译注:ConfigurationElementCollectionType 枚举,指定 ConfigurationElementCollectionType 对象的类型,包含以下4种类型:

BasicMap
此类型的集合包含应用于指定的级别(由这些元素指定)和所有子级别的元素。子级别不能修改由此类型的父元素指定的属性。

AddRemoveClearMap
ConfigurationElementCollection 的默认类型。此类型的集合包含可在配置文件的层次结构中进行合并的元素。在这类层次结构的任何特定级别中,均可使用 add、remove 和 clear 指令修改任何继承的属性和指定新的属性。

BasicMapAlternate
除了使 ConfigurationElementCollection 对象对其内容进行排序以将继承的元素排列在最后外,此类型与 BasicMap 相同。

AddRemoveClearMapAlternate
除了使 ConfigurationElementCollection 对象对其内容进行排序以将继承的元素排列在最后外,此类型与 AddRemoveClearMap 相同。

      在前面的例子里,我们创建了一个集合表示things,而且每一个thing都是通过<add>元素来添加的。对于我们的目的,完全支持级联联合可能是没有必要的,而且用<thing>作为元素的名字比<add>好。我们可以使用很多种方法来完成这个,但是我们将使用最常见的修改我们原来的ThingElementCollection类为BasicMap 并使用元素名字<thing>:

[ConfigurationCollection(typeof(ThingElement), AddItemName="thing",
      CollectionType
=ConfigurationElementCollectionType.BasicMap)]
public class ExampleThingElementCollection: ConfigurationElementCollection
{
    
#region Constructors
    
// ...
    #endregion

    
#region Fields
    
// ...
    #endregion

    
#region Properties
    
// ...

    
public override ConfigurationElementCollectionType CollectionType
    {
        
get { return ConfigurationElementCollectionType.BasicMap; }
    }

    
protected override string ElementName
    {
        
get { return "thing"; }
    }
    #endregion

    
#region Indexers
    
// ...
    #endregion

    
#region Methods
    
// ...
    #endregion
    
    
#region Overrides
    
// ...
    #endregion
}

        这些简单的修改将更新我们的集合类为BasicMap ,这将使得在.config文件中可以使用元素<thing>添加新项,而不是<add>。现在我们可以这样修改我们的配置文件,比以前的版本更漂亮、更清楚:

<configuration>
  
<configSections>
    
<section name="example" type="Examples.Configuration.ExampleSection,
                                     Examples.Configuration" />
  </configSections>

  
<example
    stringValue
="A sample string value."
    boolValue
="true"
    timeSpanValue
="5:00:00"
  
>
    
<nestedElement
      dateTimeValue
="10/16/2006"
      integerValue
="1"
    
/>
    
<things>
      
<thing name="slimy" type="goo" />
      
<thing name="metal" type="metal" color="silver" />
      
<thing name="block" type="wood" color="tan" />
    
</things>
  
</example>
</configuration>

       有时候,当我们有一个层次结构的配置,而且设置级联从一个父web.config到子web.config,我们需要控制那些元素显示在集合的前面。默认,所有元素以继承的顺序。当有多个web.config文件合并时,这种顺序不是特别好定义。通过使用替换的集合类型(译注:即BasicMapAlternateAddRemoveClearMapAlternate),我们可以控制迫使所以继承的元素被列在最后。级联三个web.config文件将以读取文件的顺序列出所有项,以最低级的web.config开始,随后是他的父web.config们,最后是根web.config。BasicMapAlternateAddRemoveClearMapAlternate都是这种方式,而且警告添加时修改父级别的web.config的项。

  8、自定义配置节组

  根据你致力于的项目类型或者特定于你的应用程序的配置需求,你可能会觉得使用配置节组更有用。.NET 2.0提供了便利的配置功能来完成这事,之前我们讨论的ASP.NET的配置组就是。创建一个配置节组比创建一个配置节更简单,但是使用和访问他们呢稍微复杂一些。假定我们有两个配置节类叫做ExampleSection 和AnotherSection,我们可以像这样写成一个配置组:

代码public sealed class ExampleSectionGroup: ConfigurationSectionGroup

  {

  
#region Constructors

  
public ExampleSectionGroup()

  {

  }

  #endregion

  
#region Properties

  [ConfigurationProperty(
"example")]

  
public ExampleSection Example

  {

  
get { return (ExampleSection)base.Sections["example"]; }

  }

  [ConfigurationProperty(
"another")]

  
public AnotherSection Another

  {

  
get { return (AnotherSection)base.Sections["another"]; }

  }

  #endregion

  }

    一旦我们有了节组代码,我们需要在.config文件中定义它。和定义一个一般的节类似,只是增加了层次级别。值得注意的是,ExampleSection 和AnotherSection现在必须作为我们配置组的子节点定义:

type="Examples.Configuration.ExampleSectionGroup,

  Examples.Configuration
">

stringValue="A sample string value."

  boolValue
="true"

  timeSpanValue
="5:00:00"

  
>

  

  dateTimeValue
="10\16\2006"

  integerValue
="1"

  
/>

      这个配置节组做了这些,使两个配置节在一个组。此外,它提供了一个访问这些节的中心点。一旦我们有一个引用指向我们的ConfigurationSectionGroup对象,我们可以访问ExampleSection 和AnotherSection,而不用再次调用ConfigurationManager.GetSection()。但是,获取最初的引用指向我们的ExampleSectionGroup并不和获取一个单独的节那样简单。专研.NET 2.0深入一点,我们将发现Configuration类。这个类直接表示一个应用程序定义在它的.config文件中的配置。跟ConfigurationManager类一样,Configuration有一个GetSection()方法,以及附加了GetSectionGroup()方法。我们可以访问配置节组和里面的配置节,例如:

代码private string m_string;

  
private bool m_bool;

  
private TimeSpan m_timespan;

  
private DateTime m_datetime;

  
private int m_int;

  void GetExampleSettings()

  {

  Configuration config
=

  ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

  ExampleSectionGroup group
= config.GetSectionGroup("example.group")

  
as ExampleSectionGroup;

  ExampleSection section
= group.Example;

  m_string
= section.StringValue;

  m_bool
= section.BooleanValue;

  m_timespan
= section.TimeSpanValue;

  m_datetime
= section.Nested.DateTimeValue;

  m_int
= section.Nested.IntegerValue;

  AnotherSection section2
= group.Another;

  }

    虽然上面的代码不是很复杂,要求一个Configuration对象来获得GetSectionGroup()方法不是很明显。一旦我们有一个Configuration对象,然而,你会发现一些他的额外有用的功能。在下一节中,我们将讨论用Configuration对象来将修改保存回配置文件,只要支持保存。

  9、保存配置更改

  到目前为止,我们已经研究了使用.NET 2.0的配置功能来定义和加载配置设置。对于许多应用程序来说,这是不够的。和使用一样,有许多时候必须保存配置。在上一节中,我们使用了Configuration对象,它提供给开发者一种将修改编程式地保存回配置节。唯一的先决条件就是配置对象允许有变更。让我们回顾一下原来的ExampleSection类,并使它可修改:

代码 #region Properties

  
///

  
/// Gets the StringValue setting.

  
///

  [ConfigurationProperty(
"stringValue", IsRequired=true)]

  
public string StringValue

  {

  
get { return (string)base[s_propString]; }

  
set { base[s_propString] = value; }

  
// Allows setting to be changed

  }

  
///

  
/// Gets the BooleanValue setting.

  
///

  [ConfigurationProperty(
"boolValue")]

  
public bool BooleanValue

  {

  
get { return (bool)base[s_propBool]; }

  
set { base[s_propBool] = value; }

  
// Allows setting to be changed

  }

  
///

  
/// Gets the TimeSpanValue setting.

  
///

  [ConfigurationProperty(
"timeSpanValue")]

  
public TimeSpan TimeSpanValue

  {

  
get { return (TimeSpan)base[s_propTimeSpan]; }

  
set { base[s_propTimeSpan] = value; }

  
// Allows setting to be changed

  }

  
///

  
/// Override the Properties collection and return our custom one.

  
///

  
public override ConfigurationPropertyCollection Properties

  {

  
get { return s_properties; }

  }

  #endregion

    正如你所看到的,更改基本的上由.NET 2.0提供的对象模型配置。简单地添加setters到配置属性上,允许他们在代码中被修改。ConfigurationElement,这是最终在每个配置类,你可以写的根源,处理所有底层复杂的东西确保修改是验证地、可转换地、有效地保存到.config文件中。要使ConfigurationElementCollection可修改,必须添加方法编辑集合。我们之前的ExampleThingElementCollection类添加一些方法使它可修改:

代码 #region Methods

  
public void Add(ThingElement thing)

  {

  base.BaseAdd(thing);

  }

  
public void Remove(string name)

  {

  base.BaseRemove(name);

  }

  
public void Remove(ThingElement thing)

  {

  base.BaseRemove(GetElementKey(thing));

  }

  
public void Clear()

  {

  base.BaseClear();

  }

  
public void RemoveAt(int index)

  {

  base.BaseRemoveAt(index);

  }

  
public string GetKey(int index)

  {

  
return (string)base.BaseGetKey(index);

  }

  #endregion

    一定要记住添加setters到配置元素集合包括的配置元素。添加一些公有的构造器对简化创建和填充配置元素到集合非常有用。一旦你已经做了必要的修改允许你的配置设置能在运行时修改,你可以调用Configuration类的Save()方法。Save()方法有三种不同的重载,允许你控制究竟要保存什么和强迫保存,即使没有变化。

  Configuration.Save() - 仅保存修改了的值

  Configuration.Save(ConfigurationSaveMode) - 保存指定类别的修改,如果存在修改

  Configuration.Save(ConfigurationSaveMode, bool) - 保存指定类别的修改,迫使进行保存,如果第二个参数为true

  ConfigurationSaveMode枚举具有以下值:

  Full - 保存所有配置属性,不管是否有所变化

  Modified - 保存已修改的属性,即使当前的值和原来是一样的

  Minimal - 保存已经修改且和原来值不一样的属性

  Configuration对象也有一个SaveAs()方法,他有跟Save()方法一样的基本重载。SaveAs()方法要求一个文件名作为第一个参数,表示要将配置文件保存到的路径和文件名。

        10、配置技巧和窍门

  在我研究和实验配置节的时候,我学到一些技巧,可是使他们更容易使用。自定义配置节的某些方面非常乏味,如所有时间一直通过调用ConfigurationManager.GetSection("someSectionName")获取SomeSectionClass。为了减轻繁琐乏味,我尝试在我的ConfigurationSection类中使用下列方式:

public class SomeConfigurationSection
{
    
static SomeConfigurationSection()
    {
        
// Preparation...
    }
    
    
// Properties...
    
    #region GetSection Pattern
    
private static SomeConfigurationSection m_section;
    
    
/// <summary>
    /// Gets the configuration section using the default element name.
    /// </summary>
    public static SomeConfigurationSection GetSection()
    {
        
return GetSection("someConfiguration");
    }
    
    
/// <summary>
    /// Gets the configuration section using the specified element name.
    /// </summary>
    public static SomeConfigurationSection GetSection(string definedName)
    {
        
if (m_section == null)
        {      
            m_section
= ConfigurationManager.GetSection(definedName) as
                        SomeConfigurationSection;
            
if (m_section == null)
                
throw new ConfigurationException("The <" + definedName +
                      
"> section is not defined in your .config file!");
        }    
        
        
return m_section;
    }
    #endregion
}

        上述的模式增加了一个静态GetSection()方法给每个自定义ConfigurationSection类。它的一个重载方法,以一个字符串作为参数,允许你为.config中元素定义一个不同的名字,如果你喜欢的话。另外,默认的重载是可以使用的。这种模式使用在标准的应用程序(.exe)的配置节下工作的非常好。然而,如果配置节使用在一个web.config文件中,你将需要使用下面的代码:

using System.Web;
using System.Web.Configuration;

public class SomeConfigurationSection
{
    
static SomeConfigurationSection()
    {
        
// Preparation...
    }
    
    
// Properties...
    
    #region GetSection Pattern
    
private static SomeConfigurationSection m_section;
    
    
/// <summary>
    /// Gets the configuration section using the default element name.
    /// </summary>
    /// <remarks>
    /// If an HttpContext exists, uses the WebConfigurationManager
    /// to get the configuration section from web.config.
    /// </remarks>
    public static SomeConfigurationSection GetSection()
    {
        
return GetSection("someConfiguration");
    }
    
    
/// <summary>
    /// Gets the configuration section using the specified element name.
    /// </summary>
    /// <remarks>
    /// If an HttpContext exists, uses the WebConfigurationManager
    /// to get the configuration section from web.config.
    /// </remarks>
    public static SomeConfigurationSection GetSection(string definedName)
    {
        
if (m_section == null)
        {
            string cfgFileName
= ".config";
            
if (HttpContext.Current == null)
            {
                m_section
= ConfigurationManager.GetSection(definedName)
                            as SomeConfigurationSection;
            }
            
else
            {
                m_section
= WebConfigurationManager.GetSection(definedName)
                            as SomeConfigurationSection;
                cfgFileName
= "web.config";
            }
                
            
if (m_section == null)
                
throw new ConfigurationException("The <" + definedName +
                  
"> section is not defined in your " +
                  cfgFileName
+ " file!");
        }    
        
        
return m_section;
    }
    #endregion
}

       正如你看到的,在ASP.NET下访问配置节需要使用System.Web.Configuration.WebConfigurationManager,而不是System.Configuration.ConfigurationManager。检查当前的HttpContext足以确定使用哪个管理器(manager)去获取配置节。还有一个增强,我们对这种模式,使得允许我们可以保存修改。我们知道,必须使用Configuration对象来保存修改,因为管理器(manager)类不提供Save()方法。我们可以在我们的GetSection方法中创建一个Configuration对象,但是最终将缺乏灵活性、效率不高。在最终完全的模式中,我这样做,把Configuration对象作为一个参数:

using System.Web;
using System.Web.Configuration;

public class SomeConfigurationSection
{
    
static SomeConfigurationSection()
    {
        
// Preparation...
    }
    
    
// Properties...
    
    #region GetSection Pattern
    
// Dictionary to store cached instances of the configuration object
    private static Dictionary<string,
            SomeConfigurationSection
> m_sections;
    
    
/// <summary>
    /// Finds a cached section with the specified defined name.
    /// </summary>
    private static SomeConfigurationSection
            FindCachedSection(string definedName)
    {
        
if (m_sections == null)
        {
            m_sections
= new Dictionary<string,
                             SomeConfigurationSection
>();
            
return null;
        }
        
        SomeConfigurationSection section;
        
if (m_sections.TryGetValue(definedName, out section))
        {
            
return section;
        }
        
        
return null;
    }
    
    
/// <summary>
    /// Adds the specified section to the cache under the defined name.
    /// </summary>
    private static void AddCachedSection(string definedName,
                   SomeConfigurationSection section)
    {
        
if (m_sections != null)
            m_sections.Add(definedName, section);
    }
    
    
/// <summary>
    /// Removes a cached section with the specified defined name.
    /// </summary>
    public static void RemoveCachedSection(string definedName)
    {
        m_sections.Remove(definedName);
    }
    
    
/// <summary>
    /// Gets the configuration section using the default element name.
    /// </summary>
    /// <remarks>
    /// If an HttpContext exists, uses the WebConfigurationManager
    /// to get the configuration section from web.config. This method
    /// will cache the instance of this configuration section under the
    /// specified defined name.
    /// </remarks>
    public static SomeConfigurationSection GetSection()
    {
        
return GetSection("someConfiguration");
    }
    
    
/// <summary>
    /// Gets the configuration section using the specified element name.
    /// </summary>
    /// <remarks>
    /// If an HttpContext exists, uses the WebConfigurationManager
    /// to get the configuration section from web.config. This method
    /// will cache the instance of this configuration section under the
    /// specified defined name.
    /// </remarks>
    public static SomeConfigurationSection GetSection(string definedName)
    {
        
if (String.IsNullOrEmpty(definedName))
            definedName
= "someConfiguration";
            
        SomeConfigurationSection section
= FindCachedSection(definedName);
        
if (section == null)
        {
            string cfgFileName
= ".config";
            
if (HttpContext.Current == null)
            {
                section
= ConfigurationManager.GetSection(definedName)
                          as SomeConfigurationSection;
            }
            
else
            {
                section
= WebConfigurationManager.GetSection(definedName)
                          as SomeConfigurationSection;
                cfgFileName
= "web.config";
            }
                
            
if (section == null)
                
throw new ConfigurationException("The <" + definedName +
                  
"> section is not defined in your " + cfgFileName +
                  
" file!");
                
            AddCachedSection(definedName, section);
        }
        
        
return section;
    }
    
    
/// <summary>
    /// Gets the configuration section using the default element name
    /// from the specified Configuration object.
    /// </summary>
    /// <remarks>
    /// If an HttpContext exists, uses the WebConfigurationManager
    /// to get the configuration section from web.config.
    /// </remarks>
    public static SomeConfigurationSection GetSection(Configuration config)
    {
        
return GetSection(config, "someConfiguration");
    }
    
    
/// <summary>
    /// Gets the configuration section using the specified element name
    /// from the specified Configuration object.
    /// </summary>
    /// <remarks>
    /// If an HttpContext exists, uses the WebConfigurationManager
    /// to get the configuration section from web.config.
    /// </remarks>
    public static SomeConfigurationSection GetSection(Configuration config,
                                           string definedName)
    {
        
if (config == null)
            
throw new ArgumentNullException("config",
                  
"The Configuration object can not be null.");
            
        
if (String.IsNullOrEmpty(definedName))
            definedName
= "someConfiguration";
            
        SomeConfigurationSection section
= config.GetSection(definedName)
                                           as SomeConfigurationSection;
                
        
if (section == null)
            
throw new ConfigurationException("The <" + definedName +
                  
"> section is not defined in your .config file!");
        
        
return section;
    }
    #endregion
}

        通过传递Configuration对象,一个可保存的配置节实例能在XML文件中检索一个指定名字的配置节。这把我带到另外一个重要的配置节秘诀。配置节元素的名字不一定必须的固定不变,也不一定只有一个配置节的实例。在一个.config文件按中每个配置节可以定义和设置多次,只要给每个实例不同的名字:

<configuration>
  
<configSections>
    
<section name="example1" type="Examples.Configuration.ExampleSection,
                                      Examples.Configuration" />
    <section name="example2" type="Examples.Configuration.ExampleSection,
                                      Examples.Configuration" />
    <section name="example3" type="Examples.Configuration.ExampleSection,
                                      Examples.Configuration" />
  </configSections>
  
  
<example1 />
  
<example2 />
  
<example3 />
</configuration>

         以同样的方式配置节组也可以定义多次。这使得一个通常的自定义配置结构在同一个应用程序中,同时以多种方式使用,而且使得自定义配置可以重用。因为可能在一个.config文件中一个配置节定义多次,最终实现上述的模式包括一个简单的实例缓存。每次用不同的definedName调用GetSection(string),将返回不同的配置节对象且存储在缓存中。连续以相同的名字调用将返回相同的缓存实例。这种模式的另一个重要方面是缺少为两个新版本的GetSection(以一个Configuration对象作为参数的GetSection方法)的缓存。用Configuration对象比用ConfigurationManager或WebConfigurationManager花费更大的开销。通过配置管理器(manager)调用GetSection()方法将完全缓存节,而通过Configuration对象调用将导致节每次都要被解析。一般来说,Configuration对象只有当需要保存配置更改是才使用。配置管理器类应该被用来访问读取配置。这将保证使用配置设置时性能非常好的。

  最后一个秘诀是关于性能的主题。除非你实现高级的配置节或元素,包括事件通知,缓存配置设置在变量中通常沮丧的(行不通的)。更多关于这个的讨论将在下面的高级部分,首先考虑以下非常简单的情景:

public class SomeProgram

  {

  
static Main()

  {

  s_config
= MyConfig.GetSection();

  s_duration
= s_config.Duration;

  s_timer
= new Timer(

  
new TimerCallback(),

  
null,

  TimeSpan.Zero

  s_duration

  );

  Console.ReadKey();

  s_timer.Dispose();

  }

  
private static MyConfig s_config;

  
private static Timer s_timer;

  
private static TimeSpan s_duration;

  
private static void WriteCurrentTime(object data)

  {

  Console.WriteLine(
"The current time is " + DateTime.Now.ToString());

  
if (s_duration != s_config.Duration)

  {

  s_duration
= s_config.Duration;

  s_timer.Change(TimeSpan.Zero, s_duration);

  }

  }

  }

    在上面的应用程序中,我们希望如果配置文件更新的话,改变定时器间隔。我们配置节的实例,s_config,将一直保持更新。因此如果在应用程序运行时,.config文件改变,任何改变将被发现并载入到内存中。如果你跟我在文章中一样实现你的配置节,覆写静态构造器和替换属性(properties)集合,这样你的集合将有有高的性能。这使得访问一个配置属性(property)相对廉价的操作,因此上述代码可以重写成如下:

public class SomeProgram
{
    
static Main()
    {
        s_config
= MyConfig.GetSection();
        
        s_timer
= new Timer(
            
new TimerCallback(),
            
null,
            TimeSpan.Zero
            s_config.Duration
        );
        
        Console.ReadKey();
        s_timer.Dispose();
    }
    
    
private static MyConfig s_config;
    
private static Timer s_timer;
    
private static TimeSpan s_duration;
    
    
private static void WriteCurrentTime(object data)
    {
        Console.WriteLine(
"The current time is " +
                          DateTime.Now.ToString());      
        s_timer.Change(TimeSpan.Zero, s_config.Duration);
    }
}

        如果这个例子过于简单揭示直接使用配置设置的意义,那么想象一个更复杂的场景,一个配置值在一个缓存变量。变量是顺序通过一个链来调用,然后循环使用。如果想要的结果对任何配置的过期及时发现并回答,那么缓存将不起作用。你必须直接访问配置属性(property),不用把它缓存到变量中。配置设置是全局访问的,可以在应用程序的任何地方。这意味着在你的代码中的任何地方都可以访问配置属性(property),而不用缓存变量的值和传递一个变量参数。将使得代码更干净,因为你要求更少的的参数。如果高性能是绝对必要的,是有可能写一个有事件的配置节,当配置数据在磁盘上已经改变能通知使用者。然而,这是一个更高级的主题,将在以后讨论。

  11、高级配置主题

  本文中概述的信息提供了一个全面地介绍.NET 2.0框架的配置功能特性。然而,这决不是一个全面的文件,并且还有一些更复杂的使用配置节。其他信息将在后面的文章:

  解码.NET 2.0配置之谜

  破解.NET 2.0配置之谜

  12、附录

  12.1、附录A: 配置结构的级联

  在ASP.NET应用程序中,web.config文件可能针对任何IIS“应用程序”。倘若应用程序的虚拟文件夹是另一个应用程序的孩子,来自父应用程序的web.config文件将和子应用程序的web.config合并。因为IIS中的应用程序可以嵌套任何级别的深度,当子应用应程序的web.config加载时,配置级联将产生。

  假设我们有一个站点安装在IIS里,以下面的层次结构且每个web.config文件包含一个共同的集合:

\wwwroot
      web.config
      
\firstapp
          web.config
      
\anotherapp
          web.config
          
\childapp
              web.config
      
\finalapp
          web.config

<!-- \wwwroot\web.config -->
<configuration>
    
<commonCollection>
        
<add key="first"  value="C98E4F32123A" />
        
<add key="second" value="DD0275C8EA1B" />
        
<add key="third"  value="629B59A001FC" />
    
</commonCollection>
</configuration>

<!-- \wwroot\firstapp\web.config -->
<configuration>
    
<commonCollection>
        
<remove key="first" />        
        
<add key="first"  value="FB54CD34AA92" />
        
        
<add key="fourth" value="DE67F90ACC3C" />
    
</commonCollection>
</configuration>

<!-- \wwroot\anotherapp\web.config -->
<configuration>
    
<commonCollection>
        
<add key="fourth" value="123ABC456DEF" />
        
<add key="fifth"  value="ABC123DEF456" />
        
<add key="sixth"  value="0F9E8D7C6B5A" />
    
</commonCollection>
</configuration>

<!-- \wwroot\anotherapp\childapp\web.config -->
<configuration>
    
<commonCollection>
        
<remove key="second" />
        
<remove key="fourth" />
        
<remove key="sixth" />

        
<add key="seventh" value="ABC123DEF456" />
        
<add key="ninth"  value="0F9E8D7C6B5A" />
    
</commonCollection>
</configuration>

<!-- \wwroot\lastapp\web.config -->
<configuration>
    
<commonCollection>
        
<clear />
        
        
<add key="first"  value="AABBCCDDEEFF" />
        
<add key="second" value="112233445566" />
        
<add key="third"  value="778899000000" />
        
<add key="fourth" value="0A0B0C0D0E0F" />
    
</commonCollection>
</configuration>

        如果我们研究了每个应用程序的集合,结果将如下:

  \wwwroot\web.config

  first = C98E4F32123A

  second = DD0275C8EA1B

  third = 629B59A001FC

  \wwwroot\firstapp\web.config

  first = FB54CD34AA92

  second = DD0275C8EA1B

  third = 629B59A001FC

  fourth = DE67F90ACC3C

  \wwwroot\anotherapp\web.config

  first = C98E4F32123A

  second = DD0275C8EA1B

  third = 629B59A001FC

  fourth = 123ABC456DEF

  fifth = ABC123DEF456

  sixth = 0F9E8D7C6B5A

  \wwwroot\anotherapp\childapp\web.config

  first = C98E4F32123A

  third = 629B59A001FC

  fifth = ABC123DEF456

  seventh = ABC123DEF456

  ninth = 0F9E8D7C6B5A

  \wwwroot\lastapp\web.config

  first = AABBCCDDEEFF

  second = 112233445566

  third = 778899000000

  fourth = 0A0B0C0D0E0F

  我希望这个简单的示例,子应用程序的web.config是如何继承设置的,这些设置是如何被覆写,足够了。你可能不是经常遇到这种情况,但是了解发生了什么,以及如何覆写父web.config的配置设置,应该有助于减轻ASP.NET开发者的配置文件问题。

  12.2、附录B: 包含外部配置文件

  尽管在.NET 2.0的配置功能中都很伟大,但是仍有一个缺点。当工作在一个多环境的单一项目中,管理配置文件是一个噩梦。管理多环境下的多版本的配置文件(如开发、测试、阶段、产品)的过程,我目前的工作包括手工比较.config文件,将更改部署到一个环境或另外一个,通过手工合并。我花了几个月试图找到一种更好的方法,最终找到了。进入这样那样一些没有“没有文档的”或很少文档的——微软著名的特点,的其中的一个:configSource。当我用Reflector深入挖掘.NET 2.0配置源码的时候,碰到这个珍品,美妙的小工具。

  每个配置节在被.NET配置类解析和加载时,都分配了一个SectionInformation对象。SectionInformation对象包含关于配置节的元信息,并允许管理节如何互相覆写,当定义在一个子web.config中时(ASP.NET)。现在,我们将忽略大部分SectionInformation对象提供的,考虑configSource属性(property)。通过添加configSource属性(attribute)到任何ConfigurationSection的根元素,你可以指定一个备用,外部的配置设置将被加载。

<configuration>
  
<connectionStrings configSource="externalConfig/connectionStrings.config"/>
</configuration>

<!-- externalConfig/connectionStrings.config -->
<connectionStrings>
  
<add name="conn" connectionString="blahblah" />
</connectionStrings>

在上面的配置文件中,节源于名为externalConfig/connectionStrings.config的文件。所有应用程序的连接字符串将加载自这个特定文件。现在,连接字符串是从外部资源加载的,在相对相同位置的每个环境,他相对简单地创建一个connectionStrings.config文件。因此externalConfig/ connectionStrings.config 文件的路径。这里漂亮的地方是,我们可以正确地为每个环境定义连接字符串定义一次。我们不用担心意外覆写那些设置,在部署一个config文件时,无论是否合并得当或根本不合并。这是一个巨大的福音,当更改一个应用程序到产品环境是,他的关键正确的数据库连接字符串存在。使用configSource属性(attribute)失效,就是它要求所有的配置节将放置在外部文件中。没有继承或覆写是可能的,在某些情况下使它没用。所有的外部配置文件用configSource属性(attribute)引用,也必须放在相对子到主的.config文件路径上。我相信这是考虑web环境中的安全性,存储文件在相对父路径上。

  别的需要注意的是节有一个更好的选择使用configSource,称为file。如果你使用file属性(attribute)而不是configSource在节里,你可以定义设置在根.config文件或引用文件都可以。根.config文件的设置也能被引用文件覆写,简单地用相同的键添加东西。可悲的是,file属性(attribute)只适用在节,而不是建立在配置框架下。在我们自己的配置节中也可能实现类似的属性(attribute)。这将在将来的高级配置主题部分讨论,几个先决部分之后。

         此英文原文:Jon RistaUnraveling the Mysteries of .NET 2.0 Configuration

0
相关文章