Configuration.ConfigurationSettings 类获取节点值浅谈

System.Configuration.ConfigurationSettings 类

相信大家对这个类都不陌生吧。 ConfigurationSettings类重要的方法是(在我下面的分析中,方法也包括属性):
AppSettings属性 用于获取 元素配置节中的配置设置
GetConfig方法 返回用户定义的配置节的配置设置

在我们的项目开发中,我们经常通过 ConfigurationSettings.AppSettings["myKey"] 的方法来获取 web.config 配置项上 appSettings 的配置值。调用这个ConfigurationSettings.AppSettings["myKey"]索引器我们就可以获取到 web.cofing 配置项 appSettings 的配置值,这太方便了。
如果要我们设计一个这样的功能的时候,我们会有什么想法呢。 我的想法大概的是这样的:
1.        加载 web.config 配置文件的内容
2.        分析 web.config 配置文件配置项 appSettings 节点的内容,并加载到配置项管理类中。
3.        配置项管理类中应该有一个索引器,方便外部系统访问。

让我们来分析大师们是如何实现这个类的。看看大师级人物的代码和设计思路有何高明之处。

//ConfigurationSettings类的定义
public sealed class ConfigurationSettings
{}

C# 关键字 sealed 表明此类是不能被继承的。

//静态构造函数
static ConfigurationSettings()
{
   _initState = InitState.NotStarted;
   _initLock = new object();
}

一个类最先运行的代码段就是静态构造函数,并且对于整个程序域而言静态构造函数只运行一次。 C#关键字 static 加上类名称的方法函数就是静态构造函数。 对于一个类来说,只能有一个静态构造函数。 静态构造函数的作用主要是初始化静态变量。 以 C#关键字 static 约束的类方法里面的代码都只能调用静态变量或者静态方法,静态属性等。
静态方法:C#关键字 static 约束的方法就是静态方法(有些教材可能会称为类方法),里面的代码都只能调用 静态变量或者静态方法,静态属性等。

//静态变量的定义代码
private static object _initLock;

C#关键字 static 表明此变量为静态变量。

//构造函数
private ConfigurationSettings()
{}

发现上面的构造函数跟我们平时所写的类的构造函数有什么不同吗?
对了,就是访问权限的约束关键字 private,平时构造函数的约束关键字都是 public 的。 那么将构造函数访问权限设置为 private有什么目的呢?
1.        防止别人的代码通过 new 操作生成对象实例。
如:System.Configuration.ConfigurationSettings config = new System.Configuration.ConfigurationSettings();
你会发现上面的代码编译不通过,原因就是访问了 private 的构造函数,当然编译不通过啦!
2.        保证一个类仅有一个实例。
这里就是设计模式中的 Singleton 单件模式了,设置构造函数的访问权限为 private 是实现 Singleton 模式的前提

 //AppSettings静态只读属性
public static NameValueCollection AppSettings
{
       get
       {
           ReadOnlyNameValueCollection config = (ReadOnlyNameValueCollection) GetConfig("appSettings");
           if (config == null)
           {
               config = new ReadOnlyNameValueCollection(new
               CaseInsensitiveHashCodeProvider(CultureInfo.InvariantCulture), new 
CaseInsensitiveComparer(CultureInfo.InvariantCulture));
          config.SetReadOnly();
            }
         return config;
   }
}

通过上面的代码我们可以知道,此属性为静态只读属性(static 关键字,只有 get 操作,而没有 set 操作)。 因为 NameValueCollection 类定义了索引访问器,平时我们都是这样写:
ConfigurationSettings.AppSettings["myKey"]

对于["myKey"]这种使用 []号访问的索引器,我们下面分析NameValueCollection类时再说明索引器。

ReadOnlyNameValueCollection config = (ReadOnlyNameValueCollection) GetConfig("appSettings");
注意到参数的值是 appSettings 了吗? 是不是跟我们 web.config 里面的 appSettings 的配置节点项有关联呢?他们有什么关系吗?我们往下看。

这段代码调用了ConfigurationSettings类的另外一个静态方法,代码如下:

public static object GetConfig(string sectionName)//当然这时 sectionName == "appSettings"
{
   if ((sectionName == null) || (sectionName.Length == 0))
  //判断 string 的值是不是为Empty时,应该用 sectionName.Length == 0 来判断
   {
      return null;
   }
   if (_initState < InitState.Usable)
   {
      EnsureConfigurationSystem();
   }

   if (_initError != null)
   {
      throw _initError;
   }
   return _configSystem.GetConfig(sectionName);
}

代码段:
if (_initState < InitState.Usable)
{
   EnsureConfigurationSystem();
}

InitState 只是一个私有的枚举类型 enum:
private enum InitState
{
   NotStarted,
   Started,
   Usable,
   Completed
}

刚才ConfigurationSettings类的静态构造函数是设置  initState = InitState.NotStarted; 那么第一次运行时,肯定会执行 EnsureConfigurationSystem()方法了,我们接着看看代码的实现

private static void EnsureConfigurationSystem()
{
   lock (_initLock)
   {
      if (_initState < InitState.Usable)
      {
         _initState = InitState.Started;
         try
         {
            _configSystem = new DefaultConfigurationSystem();
            _initState = InitState.Usable;  
         }
         catch (Exception exception)
         {
            _initError = exception;
            _initState = InitState.Completed;
            throw;
         } 
      }
   }
}
_configSystem = new DefaultConfigurationSystem();
private static IConfigurationSystem _configSystem;

_configSystem 是一个接口变量。 先看看接口 IConfigurationSystem 的定义:

public interface IConfigurationSystem
{
    // Methods
    object GetConfig(string configKey);
    void Init();
}

接着我们跟踪实现了 IConfigurationSystem 接口的 DefaultConfigurationSystem 类看看 类的定义:
internal class DefaultConfigurationSystem : IConfigurationSystem
{
}

C# 关键字 internal 表明此类只能被当前的 dll 里面的类使用。  顺便提一提 protected internal 这样的二个关键字的约束。它表明这个只能被当前 dll 里面的类使用或者不是当前 dll 里面的子类使用,记得是 或者 的关系 我们还是先从这个类的构造函数分析开始:
internal DefaultConfigurationSystem()
{}

这里的构造函数使用 internal,并不是像 ConfigurationSettings 类构造函数的 private 。 它的访问权限比 ConfigurationSettings 的类的松一点,允许当前 dll 里面的类可以通过 new 操作来生成多个 DefaultConfigurationSystem 实例。 所以这里才有上面的 _configSystem = new DefaultConfigurationSystem(); 代码调用。

重要方法GetConfig的部分关键代码内容:
object IConfigurationSystem.GetConfig(string configKey)//当然这里还是 configKey == "appSettings"
{
   if (!this._isAppConfigInited)
   {
      this.EnsureInit(configKey);
   }
   ConfigurationRecord record = null;
   if (record != null)
   {
     return record.GetConfig(configKey);
   }
   return null;
}

接下来我们就要分析 EnsureInit 方法。
private void EnsureInit(string configKey)
{
  try
  {
     ConfigurationRecord record = new ConfigurationRecord();
     bool flag2 = record.Load(this._machineFilename);
     //加载配置文件信息,这里是加载 machine.config 的信息,并不是 web.config的信息
     this._machineConfig = record;
    //....省略
  }
  catch (Exception exception)
  {
     this._initError = exception;
     ConfigurationSettings.SetInitError(this._initError);
     this._isMachineConfigInited = true;
     this._isAppConfigInited = true;
     throw;
  }
  finally
  {
     lock (this)
     {
        ConfigurationSettings.CompleteConfigInit();
        Monitor.PulseAll(this);
     }
  }
}

这里只是加载 .Net Framework 的 machine.config 全局配置信息,在这个配置信息项中,你可以找到这样的配置项。
<configuration>
<configSections>
<section name="appSettings" type="System.Configuration.NameValueFileSectionHandler, System"/>
</configSections>
<configuration>

这个配置项是什么意思呢? name="appSettings"这里表示自定义的配置项键,也就是 appSettings  type  =  "System.Configuration.NameValueFileSectionHandler, System" 表示 appSettings 这个自定义配置键的处理类是 System.Configuration.NameValueFileSectionHandler,后面的 System表示此类所在的 dll的名称。
System.Configuration.NameValueFileSectionHandler这个类必须实现 IConfigurationSectionHandler 这个接口。下面的代码就会调用这个接口里面的方法来获取配置数据。
通过调用 ConfigurationRecord.GetConfig -->> ConfigurationRecord.ResolveConfig -->> ConfigurationRecord.Evaluate

private object Evaluate(string configKey)// 当然这里的值还是 appSettings
{
    IConfigurationSectionHandler factory = this.GetFactory(configKey); //工厂模式
    object config = (this._parent != null) ? this._parent.GetConfig(configKey) : null;
    string[] keys = configKey.Split(new char[] { '/' });
    XmlTextReader reader = null;
    object obj3 = null;
    try
    {
 //加载我们的 web.config 文件
        reader = this.OpenXmlTextReader(this._filename); 
//调用自定义配置项处理类的接口方法获取数据。下面分析。
        obj3 = this.EvaluateRecursive(factory, config, keys, 0, reader); 
    }
    catch (ConfigurationException)//catch 的用法,先获取自定义的异常类,这才是好方法。
    {
        throw;
    }
    catch (Exception exception)
    {
        throw this.TranslateXmlParseOrEvaluateErrors(exception);
    }
    finally
    {
        if (reader != null)
        {
            reader.Close();
        }
    }
    //省略.....
    return obj3;
}

接下来的 GetFactory 的方法就是关键啦,为我们以后写自定义配置项类做参考了。
private IConfigurationSectionHandler GetFactory(string configKey)
{
    if ((this._factories != null) && this._factories.Contains(configKey))
    {
        object obj2 = this._factories[configKey];
        if (obj2 == RemovedFactorySingleton)
        {
            return null;
       }
        IConfigurationSectionHandler handler = obj2 as IConfigurationSectionHandler;
       //as 运算符用于执行可兼容类型之间的转换。
        //as 运算符类似于类型转换,所不同的是,当转换失败时,as 运算符将产生空,而不是引发异常。
        if (handler == null)
        {
            string typeName = (string) obj2;
            obj2 = null;
            new ReflectionPermission(PermissionState.Unrestricted).Assert();
            try
            {
                Type c = Type.GetType(typeName);
                if (c != null)
                {
                    if (!typeof(IConfigurationSectionHandler).IsAssignableFrom(c))
                    {
                        throw new ConfigurationException(SR.GetString
("Type_doesnt_implement_IConfigSectionHandler", new object[] { typeName }));
                    }
                    obj2 = Activator.CreateInstance(c, BindingFlags.CreateInstance
                                | BindingFlags.NonPublic | BindingFlags.Public
                                | BindingFlags.Instance, null, null, null);
                    //Activator 就是 Net Framework 著名的反射机制的实现类啦。
                    //Activator.CreateInstance可以动态创建某种类型的实例对象。
 //这里,它创建System.Configuration.NameValueFileSectionHandler 类对象啦
      }
            }
            catch (Exception exception)
            {
                throw new ConfigurationException(exception.Message, exception);
            }
            finally
            {
                CodeAccessPermission.RevertAssert();
           }
            //省略
        }
        return handler;
    }
    if (!this._factoriesNoInherit && (this._parent != null))
    {
        return this._parent.GetFactory(configKey);
    }
    return null;
}
 
接下来的 EvaluateRecursive 方法将会调用 System.Configuration.NameValueFileSectionHandler 类的接口方法

Create private object EvaluateRecursive
(IConfigurationSectionHandler factory, object config, string[] keys, int iKey, XmlTextReader reader)
{
    string text = keys[iKey];
    int depth = reader.Depth;
    do
    {
        if (!reader.Read())
        {
            break;
        }
    }
    while (reader.NodeType != XmlNodeType.Element);
    while (reader.Depth == (depth + 1))
    {
        if (reader.Name == text)
        {
            if (iKey < (keys.Length - 1))
            {
                config = this.EvaluateRecursive(factory, config, keys, iKey + 1, reader);
                continue;
            }
            CreatePermissionSetFromLocation(this._filename).PermitOnly();
            try
            {
                int line = reader.LineNumber;
                try
                {
                    ConfigXmlDocument document = new ConfigXmlDocument();
                    document.LoadSingleElement(this._filename, reader);
                    config = factory.Create(config, null, document.DocumentElement);
     //调用 System.Configuration.NameValueFileSectionHandler 类的方法了,
                   //下面要进入 System.Configuration.NameValueFileSectionHandler 类的分析了。
                }
                catch (ConfigurationException)
                {
                    throw;
                }
                catch (XmlException)
                {
                    throw;
                }
                catch (Exception exception)
                {
                    throw new ConfigurationException
(SR.GetString("Exception_in_config_section_handler"), exception, this._filename, line);
                }
                continue;
            }
            finally
            {
                CodeAccessPermission.RevertPermitOnly();
            }
        }
        this.StrictSkipToNextElement(reader);
    }
    return config;
}

IConfigurationSectionHandler 接口成员

由所有配置节处理程序实现,以分析配置节的 XML。返回的对象被添加到配置集合中,并通过 GetConfig 访问。
object Create(
   object parent,
   object configContext,
   XmlNode section
);

IConfigurationSectionHandler 接口是我们写自定义配置节点项时必须要实现的接口。下面再说。

NameValueFileSectionHandler 类的定义:
public class NameValueFileSectionHandler : IConfigurationSectionHandler
{
   // Methods
    public NameValueFileSectionHandler();
    public object Create(object parent, object configContext, XmlNode section);
}

NameValueFileSectionHandler.Create 方法的实现非常简单,就是获取 参数中 XmlNode section 的数据,通过 key 和 value 对应的关系 保存到 私有的 HashTable类型的变量中 section 的参数值大概是这样的
 <appSettings>
     <add key="ConnectionString" value="xxxxxxxxxx" />
        <add key="__SystemID__" value="xxxxxxxxxx" />
 </appSettings>

分析了 System.Configuration.ConfigurationSettings 类 后,来看看如何实现我们自定义的配置节点管理。 下面的示例代码我采用 VS.NET 的企业代码示例 Duwamish 项目来说明。

第一步,我们要自定义配置节点,web.config 配置节点示例如下:
<configuration>
  <configSections>
    <section name="DuwamishConfiguration" 
type="Duwamish7.Common.DuwamishConfiguration, 
Duwamish7.Common" />
  </configSections>
</configuration>

<DuwamishConfiguration> //注意这里就是自定义配置项节点了
    <add key="Duwamish.DataAccess.ConnectionString" 
value="server=.;User ID=sa;Password=password;database=Duwamish;Connection Reset=FALSE" />
    <add key="Duwamish.Web.EnablePageCache" value="True" />
    <add key="Duwamish.Web.PageCacheExpiresInSeconds" value="3600" />
    <add key="Duwamish.Web.EnableSsl" value="False" />
</DuwamishConfiguration>

第二步,让我们的自定义配置节点处理类实现IConfigurationSectionHandler接口。 在这里是 dll名称为 Duwamish7.Common的Duwamish7.Common.DuwamishConfiguration的类要实现这个接口代码:

public Object Create(Object parent, object configContext, XmlNode section)
{   
    NameValueCollection settings;   
    try
    {
        NameValueSectionHandler baseHandler = new NameValueSectionHandler();
        settings = (NameValueCollection)baseHandler.Create(parent, configContext, section);
    }
    catch
    {
        settings = null;
    }   
    if ( settings == null )
    {
        dbConnectionString = DATAACCESS_CONNECTIONSTRING_DEFAULT;
        pageCacheExpiresInSeconds = WEB_PAGECACHEEXPIRESINSECONDS_DEFAULT;
        enablePageCache = WEB_ENABLEPAGECACHE_DEFAULT;
        enableSsl  = WEB_ENABLESSL_DEFAULT;
    }
    else
    {
        dbConnectionString  = ApplicationConfiguration.ReadSetting
(settings, DATAACCESS_CONNECTIONSTRING, DATAACCESS_CONNECTIONSTRING_DEFAULT);
        pageCacheExpiresInSeconds = ApplicationConfiguration.ReadSetting
(settings, WEB_PAGECACHEEXPIRESINSECONDS, WEB_PAGECACHEEXPIRESINSECONDS_DEFAULT);
        enablePageCache = ApplicationConfiguration.ReadSetting
(settings, WEB_ENABLEPAGECACHE, WEB_ENABLEPAGECACHE_DEFAULT);
        enableSsl  = ApplicationConfiguration.ReadSetting(settings, WEB_ENABLESSL, WEB_ENABLESSL_DEFAULT);
    }   
    return settings;
}

第三步,如何启动我们的自定义配置节点处理类:
System.Configuration.ConfigurationSettings.GetConfig("DuwamishConfiguration");

到此,我们就搞定了自定义配置节点管理了。自定义配置节点管理可以避免将很多的配置信息都放到 appSettings 节点中,便于管理。通过类来管理配置信息,可以在多个项目中重用,我们也可以写一个基类,来获取统一的配置项键管理。如在我们的项目中差不多每个 web.config 都会有

 <add key="__SystemID__" value="xxxx" />
 <add key="__SystemName__" value="xxxx" />
 <add key="__SystemPassword__" value="xxxx" />

如果我们的基类提供获取这三个对应 key值的配置项,其它的项目需要扩展时,只须继承我们的配置节点管理基类就行了.