.NET配置系统 - 剖析AppSettings实现
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是只读的。
比如将文章最上方的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>