.NET配置系统 - 剖析AppSettings实现

来源:岁月联盟 编辑:exp 时间:2011-07-23

 

1. 浏览AppSettings

AppSettings为程序员提供了方便简洁的配置存储,下面是一个典型的AppSettings在应用程序的配置文件:

 

<?xml version="1.0" encoding="utf-8" ?>

 

<configuration>

 

  <appSettings>

 

    <clear/>

 

    <add key="key1" value="value1"/>

 

    <add key="key2" value="value2 A"/>

 

    <add key="key2" value="value2 B"/>

 

  </appSettings>

 

 

 

</configuration>

 

 

 

<configuration>是.NET配置系统的XML根节点,接着<appSettings>中<clear/>上删除继承下来的映射AppSettings值(如果有的话),通过<add>来添加一对键值,如果两个键相同,如例子中的两个key2,那么之前的键值会被后来的改写,即这个配置文件AppSettings中key2键的值是value2 B.

 

 

 

AppSettings是一个ConfigurationSection,那么我们首先可以通过Configuation的GetSection可以浏览其内容,当然Configuration类提供一个AppSettings属性来方便用户,看其源代码,其实就是用GetSection来返回”appSettings”ConfigurationSection;

 

        //Configuration类的AppSettings属性源代码

 

        public AppSettingsSection AppSettings

 

        {

 

            get

 

            {

 

                return (AppSettingsSection)this.GetSection("appSettings");

 

            }

 

        }

 

 

 

那么通过Configuration类浏览AppSettings就是这样的:

 

(代码1:通过Configuration.GetSection浏览)

 

            Configuration conf =

 

                ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

 

            AppSettingsSection appSet = conf.AppSettings;

 

            foreach (KeyValueConfigurationElement ele in appSet.Settings)

 

                Console.WriteLine("键:{0} 值:{1}", ele.Key, ele.Value);

 

 

 

 

 

看到这里,有些读者可能发现这个并不是最常用的浏览方法,是的,上述方法是浏览.NET配置文件的最原始方法,优点是灵活可读可写,缺点是有些复杂,对于AppSettings这种方便简洁的配置存储,我们还可以使用另一种更常见的方法,就是用ConfigurationManager类。这个类的AppSettings属性返回一个NameValueCollection(此类在System.Collections.Specialized命名空间内)可以直接供用户查询。

 

(代码2:通过ConfigurationManager浏览)

 

            System.Collections.Specialized.NameValueCollection nvc =

 

                ConfigurationManager.AppSettings;

 

            foreach (string key in nvc.AllKeys)

 

                Console.WriteLine("键:{0} 值:{1}", key, nvc[key]);

 

 

 

两种方法输出都一样:

 

键:key1 值:value1

 

键:key2 值:value2 B

 

 

 

 

 

 

 

2. 寻找AppSettings根源

我们说AppSettings本质上是一个ConfigurationSection,接下来是时候探索一下这个ConfigurationSection了,先来看看列举一下本地应用程序配置文件继承下来的所有ConfigurationSection。

 

这里主要通过Configuration类的Sections和SectionGroups属性来枚举

 

            Configuration conf = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

 

            Console.WriteLine("本地配置文件: {0}/n", conf.FilePath);

 

            Console.WriteLine("=== 所有SectionGroup");

 

            foreach (ConfigurationSectionGroup grp in conf.SectionGroups)

 

                Console.WriteLine(grp.SectionGroupName);

 

 

 

            Console.WriteLine();

 

            Console.WriteLine("=== 所有Section");

 

 

 

            foreach (ConfigurationSection sec in conf.Sections)

 

                Console.WriteLine(sec.SectionInformation.SectionName);

 

输出结果:

 

本地配置文件: E:/My Documents/Visual Studio 2008/Projects/TTC/TTC/bin/Release/Mg

 

en.exe.Config

 

 

 

=== 所有SectionGroup

 

system.serviceModel

 

system.net

 

system.transactions

 

system.web

 

system.runtime.serialization

 

system.serviceModel.activation

 

system.xml.serialization

 

 

=== 所有Section

 

system.data

 

windows

 

system.webServer

 

mscorlib

 

system.data.oledb

 

system.data.oracleclient

 

system.data.sqlclient

 

configProtectedData

 

satelliteassemblies

 

system.data.dataset

 

startup

 

system.data.odbc

 

system.diagnostics

 

runtime

 

system.codedom

 

system.runtime.remoting

 

connectionStrings

 

assemblyBinding

 

appSettings

 

system.windows.forms

 

 

 

AppSettings必须在其中(黄色标注)

 

 

 

我们的本地应用程序配置文件里没有定义AppSettings区域,但却可以直接使用,这是就是因为AppSettings是本地应用程序配置文件所继承的配置选项,更准确的说,继承自.NET配置系统中最顶端的配置文件machine.config

 

 

 

machine.config的文件路径可以通过如下两种方式得到,结果都是一样的

 

            var path1 = ConfigurationManager.OpenMachineConfiguration().FilePath;

 

            var path2 = new ConfigurationFileMap().MachineConfigFilename;

 

 

 

打开machine.config,在开头就可以找到AppSettings的定义(当然还有另外一个常见的ConfigurationSection:ConnectionStrings区域的定义)

 

<configuration>

 

  <configSections>

 

    <section name="appSettings"

 

             type="System.Configuration.AppSettingsSection, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

 

             restartOnExternalChanges="false"

 

             requirePermission="false"/>

 

    <section name="connectionStrings"

 

             type="System.Configuration.ConnectionStringsSection, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

 

             requirePermission="false"/>

 

    <section name="mscorlib"

 

             type="System.Configuration.IgnoreSection, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

 

             allowLocation="false"/>

 

 

 

    后续内容省略……

 

 

 

好了,看到这里,一切就”真相大白”了,AppSettings其实就是一个普普通通的System.Configuration.AppSettingsSection的配置文件定义!下面我们就开始看看这个AppSettingsSection类!

 

 

 

 

 

 

 

3. 探索AppSettingsSection

AppSettings是微软预定义在.NET Framework里的,但并没有享受什么特殊服务,就是一个由AppSettingsSection代表的普通ConfigurationSection,来看看这个类。

 

首先别忘了ConfigurationSection继承自ConfigurationElement,后者有Properties属性是ConfigurationPropertyCollection类型,用来添加用户自定义属性(这个属性是配置文件中的节点属性,当然实现起来也使用.NET的普通属性形式)。

 

AppSettingsSection只有两个属性,File和Settings。File属性大家都知道,MSDN上也有说明,但Settings属性却几乎没见过,是因为Settings属性是默认容器属性(IsDefaultCollection = true),它的ConfigurationProperty名称索性是空字符串。

 

Settings属性(ConfigurationProperty)是被这样初始化的:

 

            //AppSettingsSection的EnsureStaticPropertyBag方法 部分代码

 

            //此方法会在Properties属性的get中被调用

 

            s_propAppSettings =

 

                new ConfigurationProperty(

 

                    null,

 

                    typeof(KeyValueConfigurationCollection),

 

                    null,

 

                    ConfigurationPropertyOptions.IsDefaultCollection);

 

 

 

 

 

这样的话,向AppSettingsSection里直接添加数据,其实就是向AppSettingsSection中Settings这个KeyValueConfigurationCollection添加数据。

 

 

 

 

 

 

 

4. 探索KeyValueConfigurationCollection

KeyValueConfigurationCollection继承与ConfigurationElementCollection,并且是默认的AddRemoveClearMap类型的容器,这里必须要说一下ConfigurationElementCollection的ThrowOnDuplicate属性。

 

ConfigurationElementCollection的ThrowOnDuplicate针对AddRemoveClearMap和AddRemoveClearMapAlternate是返回true的。

 

可以参考其源代码:

 

        protected virtual bool ThrowOnDuplicate

 

        {

 

            get

 

            {

 

                if ((this.CollectionType != ConfigurationElementCollectionType.AddRemoveClearMap) && (this.CollectionType != ConfigurationElementCollectionType.AddRemoveClearMapAlternate))

 

                {

 

                    return false;

 

                }

 

                return true;

 

            }

 

        }

 

 

 

但KeyValueConfigurationCollection将其改写为false,这个非常重要,这样AppSettings中的键值可以重复添加并且如果有相同键的话,值会被覆盖!

 

 

 

另外,由于ConfigurationElementCollection的容器操作函数都是只针对继承类可见的,因此KeyValueConfigurationCollection还定义了Add,Remove,Clear公有函数,这三个函数使用ConfigurationElementCollection内部继承类可见函数BaseAdd, BaseRemove, BaseClear。用户可以使用这三个函数对AppSettings进行动态修改,这也是上面讲到过的,只有通过Configuration类才可以做到,而ConfigurationManager的AppSettings是只读的。

image

 

 

 

 

比如将文章最上方的AppSettings中的key1键移除并保存config文件:

 

            Configuration conf = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

 

            AppSettingsSection appsetSection = conf.AppSettings;

 

 

 

            KeyValueConfigurationCollection collec = appsetSection.Settings;

 

            collec.Remove("key1");

 

            conf.Save();

 

 

 

 

 

另外KeyValueConfigurationCollection也没有改写ConfigurationElementCollection的Add/Remove/ClearElementName,三者保持默认值,即:add, remove, clear

 

 

 

最后KeyValueConfigurationCollection存储KeyValueConfigurationElement,这个定义在ConfigurationElementCollectionAttribute特性中

 

    [ConfigurationCollection(typeof(KeyValueConfigurationElement))]

 

    public class KeyValueConfigurationCollection : ConfigurationElementCollection

 

这个KeyValueConfigurationElement也是普通的ConfigurationElement,这里定义两个数据属性,Key和Value分别代表键和值。

 

 

 

 

 

 

 

总述

最后,让我们再来看看文章开头的那个AppSettings,此时你应该对AppSettings有了全新的认识,我把原理加成注释:

 

<?xml version="1.0" encoding="utf-8" ?>

 

 

 

<!-- 这是.NET配置文件根节点-->

 

<configuration>

 

 

 

  <!-- machine.config中定义这个appSettings区域-->

 

 

 

  <!-- 代表AppSettingsSection这个ConfigurationSection -->

 

 

 

  <appSettings>

 

 

 

    <!-- 这里设置AppSettingsSection中的Settings属性-->

 

 

 

    <!-- 由于Settings属性具有默认容器属性,因此不需要指定名称-->

 

 

 

    <!-- 操作KeyValueConfigurationCollection(Settings属性代表的类型)-->

 

 

 

 

 

    <!-- 这是ConfigurationElementCollection中的ClearElementName -->

 

 

 

    <!-- KeyValueConfigurationCollection没有改写此值,保留默认-->

 

 

 

    <!-- 删除一切可能继承来的appSettings值-->

 

 

 

    <clear/>

 

 

 

 

 

    <!-- 在KeyValueConfigurationCollection中添加KeyValueConfigurationElement -->

 

 

 

    <!-- add是ConfigurationElementCollection的AddElementName的默认值-->

 

 

 

    <!-- key和value是KeyValueConfigurationElement的属性-->

 

 

 

    <add key="key1" value="value1"/>

 

 

 

 

 

    <!-- 同上-->

 

 

 

    <add key="key2" value="value2 A"/>

 

 

 

 

 

    <!-- 由于KeyValueConfigurationCollection改写ThrowOnDuplicate为false -->

 

 

 

    <!-- 因此相同键会被改写,此时appSettings中key2键的值被改写成value2 B -->

 

 

 

    <add key="key2" value="value2 B"/>

 

 

 

  </appSettings>

 

 

 

</configuration>