前阵子想改进产品的配置文件结构,因此研究了一下Configuration程序集,看看能不能用来做基础,现总结如下。
1. 概述
在.NET Framework中,微软提供了Configuration程序集,包含用于处理配置数据的编程模型的类型。
1.1 基本结构
Configuration的基本结构如下:
ConfigurationManager是配置信息的读写类,对于一些内置的配置信息,可以通过该类从标准配置文件直接获取;另外,也可以将指定的配置文件读取到一个Configuration对象中,再从Configuration中读取结构化的配置信息。
Configuration对象是配置文件在内存中的存储容器。可以通过Configuration对象来读取配置信息,也可以更改Configuration中的配置结构或数值来更新配置信息。
ConfigurationSection和ConfigurationSectionGroup是配置信息在内存中的结构化描述,通过这两者可以在内存中形成一棵配置信息树。
ConfigurationSection中可定义一组键值对,相当于INI文件中一个SECTION段,是配置项的基本组织单元;
ConfigurationSectionGroup是子ConfigurationSection和子ConfigurationSectionGroup的集合,ConfigurationSectionGroup将所有配置数据链接起来形成层次树状结构。从配置信息树的角度来看,可以认为ConfigurationSection是叶子节点,ConfigurationSectionGroup则是非叶子节点,一个叶子节点中保存有一组配置项。
1.2 配置文件及格式
Configuration提供了内存中的配置数据的结构化模型及其维护、存储功能。使用者只需关心内存中的结构化数据,无需了解其如何保存至文件和从文件解析。当然,对其配置文件格式稍加了解也全无坏处。
注:也就是说使用Configuration程序集来读取、生成的配置文件的格式是固定的,使用时,配置文件也是透明的,我们只需操作内存中的配置数据即可。
Configuration使用XML来保存配置数据,以configuration元素为根节点。主要由两部分数据组成:结构描述部分和数据存储部分。
configuration的第一个子节点configSections保存配置数据的结构信息,其子元素可以为section和sectionGroup(这些子元素对应于内存中Configuration对象的Sections和SectionGroups集合中的ConfigurationSection和ConfigurationSectionGroup对象),sectionGroup也可以包含子section和子sectionGroup(同样对应于ConfigurationSection和ConfigurationSectionGroup对象,其容器则是父ConfigurationSectionGroup对象的Sections和SectionGroups集合)。
每个section和sectionGroup都有这些属性:
name – 用于标识本段(组)的关键字。在XML文件的数据区域,将以此值作为该段(组)数据的元素名;在内存中,此值也是从父对象的Sections(或SectionGroups)中检索本段(组)的关键字。
Type – 保存本段(组)在内存中映射的ConfigurationSection(或ConfigurationSectionGroup)对象的类型名,由于Sections(或SectionGroups)中对象可以是ConfigurationSection(或ConfigurationSectionGroup)及其派生类型,因此,需要在XML文件保存对象的真实类型信息,以在读取的时候还原出原类型的对象。
总之,section和sectionGroup元素与内存中的ConfigurationSection和ConfigurationSectionGroup对应,文件中通过这两个元素来描述与内存中一致的配置数据结构。
在configuration元素之后便是数据部分,configuration中定义的每个section和sectionGroup在本部分都有一个元素与之对应,其元素名是相关section元素或sectionGroup元素的name属性值。其层次包含关系与结构区相同,只是元素名不一样。
每个ConfigurationSection中的配置项对应数据区相关元素的一个属性,其关键字对应属性名,值对应属性值。需要注意的是,若未在内存中更改过ConfigurationSection中的配置项值,则文件中相关元素不会有对应的属性。若ConfigurationSection中的所有配置项均保持为默认值未被更改过,则文件中不会保存相关的数据元素。
2. Configuration
2.1 Configuration与配置文件的对应关系
Configuration对象并不严格与XML配置文件一一对象。MSDN说该类表示适用于特定计算机、应用程序或资源的配置文件,又说Configuration 类实例表示配置设置的合并视图。意指Configuration对象可以是多个配置文件中配置信息的并集。
事实上,如果我们用OpenExeConfiguration打开一个应用程序的配置文件,得到的Configuration对象不仅包含指定文件中的配置信息,也将包含所有的MachineConfiguration(计算机配置信息),想必OpenExeConfiguration时会自动打开本机的MachineConfiguration并与所指定的配置文件数据合并。
Configuration.Save方法会将Configration中各个配置段和配置组写回其所属的各个文件;Configuration.SaveAs则将所有配置段和配置组合并另存到一个文件中。
2.2 有关ConfigurationManager.OpenExeConfiguration
初用Configuration时,我被这个函数折腾得相当无语。那时我还没领会到中间那个“Exe”的涵义,根据MSDN的说明“将指定的客户端配置文件作为 Configuration 对象打开”,我自然而然地认为这个函数的参数就是配置文件的文件名。于是我就发现了一个很奇怪的现象,我把x.config传进去,程序运行完后,目录下就多了一个名为x.config.config的文件,而且新的数据都是保存到此新文件下,而x.config却毫无变化。查看读取到的Configration对象的FilePath属性,也是x.config.config。刚开始的时候,我以为这是因为参数不应带文件类型后缀,于是,把x传递给此函数,结果是,运行时出“找不到文件”的异常提示。
经过多方寻问和查找资料,终于明白了其中缘由,OpenExeConfiguration的参数应为要打开的配置文件对应的应用程序路径,即传递的参数是程序文件路径而不是配置文件路径;OpenExeConfiguration只检查指定的程序文件是否存在,若不存在则会异常;OpenExeConfiguration以<app.exe> + <.config>的规则来确定应用程序对应的配置文件名,若不存在该文件,则将自动创建。
许多人遇到过同样的问题,比较一致的看法是,由于函数是新增的,可能沿袭了其它几个函数的一些逻辑。从使用上来说,这样的逻辑多少有些怪异和非必要,大家想要的只是一个打开任意配置文件的函数,这个配置文件无论是从内容还是文件名都无需与某个程序文件关联。
解决此问题的一个办法是:假如我们需要打开的配置文件是x.config,则可以在同目录下先建立一个x文件,这个文件没有任何作用,只是用于应付OpenExeConfiguration的文件存在检查而已。
3. ConfigurationSection和ConfigurationSectionGroup
3.1 配置结构的描述 - 声明性模型
根据配置文件解析得到的一个ConfigurationSection对象,其Properties属性包含文件中所保存的相关元素的所有属性,即配置项。从思路上来说,用这样的代码来获得配置项是正确的:
但是这段代码无法编译,因为Properties是受保护属性。为何会这样?因为我们有更好的使用方式:如下所示,从ExConfigurationSection派生出一个类,在其中定义一个Item1属性。
有了这个类,我们就可以直接通过Item成员来获取配置数据,相对于之前的方式,这有几个优点:一、直观;二、索引属性的调用和类型转换被封装在CustomSection1内部,不必到处书写关键字“Item1”;三、可获得编辑器的智能感知支持,更不容易出错。
对于ConfigurationSectionGroup,情况同样类似。其子段和子组分别保存在Sections和SectionGroups中,我们可以直接用关键字索引需要的子段或子组,也可以为每个子段和子组定义一个属性,将索引属性的调用和强制转换等封装其中。例:
这就是定义配置结构的声明性模型。通过把关键字隐藏在配置模型内部,可以避免关键字满天飞的情况;使用时,直观的引用方式和IDE的支持,将使代码编写更为容易并且更不容易出错。
使用这种方式,我们不直接使用ConfigurationSection和ConfigurationSectionGroup,而是从其派生,向其添加表示配置数据的成员。现在我们知道配置文件中的section和sectionGroup元素为什么会有type属性了,那是为了读取配置文件时,正确的还原出相应类型的对象。
此方式用类型信息来描述配置数据的结构模型。这样,配置信息的结构将固化在代码文件和程序集里,而不仅仅是在设计者的脑袋里和根据配置文件的内容来生成。显然,前者比后两者要可靠得多,而且另有一个隐含的好处:我们可以通过反射来处理包含定义配置模型的程序集,从中分析出配置数据的结构。某些时候,这或许会有用处。
注:另有一种称为编程模型的定义配置结构的方式,暂时未得其奥秘。
3.2 标准配置信息
.NET Framework内置了一些标准配置信息,包括MachineConfiguration、AppSettings和ConnectionStrings。
MachineConfiguration是计算机配置信息,适用于本机所有应用程序,可用ConfigurationManager.OpenMachineConfiguration方法打开。MachineConfiguration是从当前运行的 .NET Framework 版本的 Machine.config 文件中读取的。在我机器上,该文件大约包含20来个段,Machine.config 文件位于下面的子目录中:
%windir%\Microsoft.NET\Framework\version\config
AppSettings是当前应用程序的配置数据段,可用ConfigurationManager. AppSettings引用该段。从程序文件目录下的app.exe.config中读取。该文件初始不存在,向AppSettings添加内容后,该文件将被创建。查看该文件,可以发现其格式似乎与1.2中所述不同,那是因为其中的configuration元素即结构部分被忽略,而只剩数据区了。
Configuration对象也有一个AppSettings属性,若我们用OpenExeConfiguration打开一个配置文件,得到的Configuration对象的AppSettings属性对应该配置文件中的appSettings段。向Configuration.AppSettings添加一个配置项,再保存,即可在该配置文件中看到appSettings的声明(此时不会被忽略)。appSettings的内存类型为System.Configuration.AppSettingsSection。
可以推测ConfigurationManager.AppSettings和Configuration.AppSettings是同一类型的配置段,但ConfigurationManager.AppSettings并不是AppSettingsSection对象,而是AppSettingsSection的Settings成员。
ConnectionStrings是从AppConfiguration中剥离出来的有关数据库连接的配置信息。其位置与AppSettings相同。