掌握 .NET 1.1 的配置文件用法

在 .NET 1.1 中,我们都知道可以使用 app.config 或者 web.config (ASP.NET) 来保存一些设置。可是对于大多数人来说,可能用的最多的只是把它当作一个简单的 ini 文件来存储 key-value 键值对,比如数据库链接字符串,上传文件路径之类的。但是实际上配置文件里可以存放任意复杂的结构。如果读过 DNN,.Text 之类程序的代码,就可以找到这些应用的范例。不过这些项目的代码一般都比较繁杂,因此这里我结合 .Text 的配置方法,对配置文件的用法来做一个简单的小结。 

一、最简单的写法,只用到 appSettings 元素。

appSettings 里的设定在 ConfigurationSettings 类里有默认的属性来访问,他返回的是一个 NameValueCollection 子类的实例。所以通常简单的字符串值可以保存在这里。写法如下:

<? xml version="1.0" encoding="utf-8"  ?>
< configuration >
    
<!--  最简单的,在 appSettings 里面写  -->
    
< appSettings >
        
<!--  定义两个键值  -->
        
< add  key ="key1"  value ="123"   />
        
< add  key ="key2"  value ="456"   />
    
</ appSettings >
</ configuration >

读取的代码:
string  key1  =  ConfigurationSettings.AppSettings[ " key1 " ];
string  key2  =  ConfigurationSettings.AppSettings[ " key2 " ];

二、稍微加点料。。

appSettings 中不仅仅可以用 add 来添加键值,还可以用 clear 或 remove 元素。

clear 的意思是,去除父层次的配置文件中定义的所有键值。

所谓“父层次”的意思是,比如我们在 ASP.NET 中,当我们用 ConfigurationSettings.AppSettings[key] 去读取一个值的时候,首先会去检查 machine.config 里是否有此键值的配置,然后再去读取 web.config. 另外,如果在不同的目录层次中配置 web.config,则子目录中 web.config 的配置会覆盖父目录中的设置。那么这里 machine.config 相对于当前的 web.config, 或者父目录的 config 文件相对于子目录的 config 文件,就是一个父子层次的关系。

remove 则可以移除一个父层次中设定的键值。

加入这两种语法后的配置文件如下:

<? xml version="1.0" encoding="utf-8"  ?>
< configuration >
    
<!--  最简单的,在 appSettings 里面写  -->
    
< appSettings >
        
<!--  这个命令可以删除更高层次中已经定义的所有设置  -->
        
< clear  />
        
<!--  这个命令删除一个设置  -->
        
< remove  key ="somekey"   />
        
<!--  添加设置  -->
        
< add  key ="key1"  value ="123"   />
        
< add  key ="key2"  value ="456"   />
    
</ appSettings >
</ configuration >

(注:remove 和 clear 同样适用于下面将要提到的 section 和 sectionGroup 定义的元素当中可以用 add 的地方,不再一一阐述)

三、节处理器 (Section Handlers)

在配置文件里除了 appSettings, 还可以自已写 XML 格式的配置元素,这些元素叫做节(Section)。当然,如果你自己写一堆复杂的 XML 格式的标签,.NET 自身是不知道如何解析的,因此这里就需要你在指定节的同时,告诉 .NET 如何处理它们,也就是定义“节处理器”(Section Handlers)。

每一个自定义的节,都需要在 configSections 下面定义它们的节处理器。先来看一个例子:
<? xml version="1.0" encoding="utf-8"  ?>
< configuration >
    
<!--  这个里面用来定义节处理器  -->
    
< configSections >
        
< section  name ="dicValues"  type ="System.Configuration.DictionarySectionHandler"   />         
    
</ configSections >
    
    
<!--  这是一个自定义的节  -->
    
< dicValues >         
        
< add  key ="key1"  value ="abc"   />
        
< add  key ="key2"  value ="def"   />     
    
</ dicValues >
</ configuration >

这里定义的节使用的是 .NET Framework 里已有的一个类: DictionarySectionHandler.

因为这些自定义的 SectionHandler 都要提供给 ConfigurationSettings 类使用,因此它们都要实现 IConfigurationSectionHandler 接口。(具体原因可以用 Reflector 查看 ConfigurationSettings 的 GetConfig 方法,一路追踪下去即可找到答案)。

对于一些常见形式的数据,系统内部定义了几种 handler, 其用法详细叙述如下:

1. DictionarySectionHandler

这个类型的 handler 的 GetConfig 方法返回一个 Hashtable 类型的对象。配置方法见上面一个 xml . 我们可以这样写代码来访问其中的设定:
object  o  =  ConfigurationSettings.GetConfig( " dicValues " );
Hashtable ht 
=  (Hashtable) o;

foreach  ( string  key  in  ht.Keys)
{
    MessageBox.Show(key 
+   "  =  "   +  ht[key]);
}

2. NameValueSectionHandler

config 文件里设定的方法跟 DictionarySectionHandler 类似:
<? xml version="1.0" encoding="utf-8"  ?>
< configuration >
    
< configSections >
        
< section  name ="nameValues"  type ="System.Configuration.NameValueSectionHandler"   />         
    
</ configSections >
    
    
< nameValues >         
        
< add  key ="key1"  value ="abc"   />
        
< add  key ="key2"  value ="def"   />     
    
</ nameValues >
</ configuration >

但是 GetConfig 方法返回的是一个 NameValueCollection 对象:
NameValueCollection c  =  (NameValueCollection) ConfigurationSettings.GetConfig( " nameValues " );
foreach  ( string  key  in  c.Keys)
{
    MessageBox.Show(key 
+   "  =  "   +  c[key]);
}

3. SingleTagSectionHandler

这种类型的元素表现为一个简单元素,只有属性而没有子节点。各个属性的值,将会在读取时存到一个 Hashtable 中返回。配置文件如下:
<? xml version="1.0" encoding="utf-8"  ?>
< configuration >
    
< configSections >
        
< section  name ="singleTag"  type ="System.Configuration.SingleTagSectionHandler"   />         
    
</ configSections >
    
    
< singleTag  a ="hello"  b ="ok"  c ="haha"   />
</ configuration >

读取:
Hashtable ht  =  (Hashtable) ConfigurationSettings.GetConfig( " singleTag " );
foreach  ( string  key  in  ht.Keys)
{
    MessageBox.Show(key 
+   "  =  "   +  ht[key]);
}


4. IgnoreSectionHandler

 有时候需要定义一些元素,不准备由 ConfigurationSettings 类来处理,而是在外部处理。这时候为了避免产生异常,用这个 Handler 来声明,可以让 ConfigurationSettings 类读取的时候忽略该元素。这个用得比较少。

5. 自定义节处理器

通过实现 IConfigurationSectionHandler 接口,我们可以实现自己的 SectionHandler,在其中保存复杂的设定信息。最常见的是结合序列化来使用。
比如我们需要在配置文件里保存如下信息:

<? xml version="1.0" encoding="utf-8"  ?>
< configuration >
    
<!--  配置节定义部分  -->     
    
< configSections >
        
<!--  指定一个叫做 StudentSettings 的元素,其处理程序是 ConfigDemos.Config1.StudentSettingsSectionHandler.
        注意:这些程序都必须继承自 IConfigurationSectionHandler 接口。
        
        这个元素还可以有两个属性:allowDefinition, allowLocation,其含义自己看 msdn.
        
-->
        
< section  name ="StudentSettings"  type ="Config1.StudentSettingsSectionHandler, Config1"   />
    
</ configSections >
    
    
<!--  实际的数据部分  -->
    
< StudentSettings >
        
< students >
            
< Student >
                
< name > 张三 </ name >
                
< age > 20 </ age >
            
</ Student >
            
< Student >
                
< name > 李四 </ name >
                
< age > 30 </ age >
            
</ Student >
        
</ students >
    
</ StudentSettings >
</ configuration >

我们要在其中保存一组学生的信息,每一个学生有名字,年龄等信息。首先我们实现学生类,以及相应的 settings 类:
namespace  Config1
{
    
using  System;
    
using  System.Xml.Serialization;

    [Serializable]
    
public   class  Student
    {
        
private   string  name;
        
private   int  age;

        
//  表示要将此属性序列化为一个元素,而不是属性
        [XmlElement( " name " typeof  ( string ))]
        
public   string  Name
        {
            
get  {  return  name; }
            
set  { name  =  value; }
        }

        
//  意义同上
        [XmlElement( " age " typeof  ( int ))]
        
public   int  Age
        {
            
get  {  return  age; }
            
set  { age  =  value; }
        }
    }

    [Serializable]
    
public   class  StudentSettings
    {
        
private  Student[] students;

        
//  这个 attribute 指示该属性序列化为 xml 的时候,以多个子元素的形式表现
        [XmlArray( " students " )]
        
public  Student[] Students
        {
            
get  {  return  students; }
            
set  { students  =  value; }
        }
    }
}

接着我们实现一个节处理器如下,这个类名字和 config 里定义的是对应的:
namespace  Config1
{
    
using  System.Configuration;
    
using  System.Xml;
    
using  System.Xml.Serialization;

    
public   class  StudentSettingsSectionHandler : IConfigurationSectionHandler
    {
        
#region  IConfigurationSectionHandler 接口的实现

        
public   object  Create( object  parent,  object  configContext, XmlNode section)
        {
            XmlSerializer ser 
=   new  XmlSerializer( typeof  (StudentSettings));
            
object  students  =  ser.Deserialize( new  XmlNodeReader(section));
            
return  students;
        }

        
#endregion
    }
}

好了,我们现在可以用下面的代码来读取设置了。
object  o  =  ConfigurationSettings.GetConfig( " StudentSettings " );
StudentSettings settings 
=  (StudentSettings) o;

for  ( int  i  =   0 ; i  <  settings.Students.Length; i ++ )
{
    Student student 
=  settings.Students[i];
    MessageBox.Show(student.Name 
+   " "   +  student.Age);
}

以上的实现虽然比较可行,但是考虑到序列化是一个很普遍的操作,而我们在 StudentSettingsSectionHandler 类的 Create 方法里,写死了 StudentSettings 这个类型。这里显然有一种不能重用的 bad smell,比如我现在需要序列化另一个设定类型的实例,岂不是又要重新写一个这样的类?
解决这个的办法是让设置类的类型变得可以配置,这个其实在 .Text 中已经有了一个很好的实现了,看一下代码:
namespace  Dottext.Framework.Util
{
    
using  System;
    
using  System.Configuration;
    
using  System.Xml;
    
using  System.Xml.Serialization;
    
using  System.Xml.XPath;

    
public   class  XmlSerializerSectionHandler : IConfigurationSectionHandler
    {
        
public   object  Create( object  parent,  object  configContext, XmlNode section)
        {
            XPathNavigator nav 
=  section.CreateNavigator();
            
string  typename  =  ( string ) nav.Evaluate( " string(@type) " );
            Type t 
=  Type.GetType(typename);
            XmlSerializer ser 
=   new  XmlSerializer(t);
            
return  ser.Deserialize( new  XmlNodeReader(section));
        }
    }
}

这个代码里读取了当前节点的 type 属性,用反射的方式来创建类型。相应的配置文件里这样写就可以了:
< BlogConfigurationSettings  type ="Dottext.Framework.Configuration.BlogConfigurationSettings, Dottext.Framework" >
    
<!--  内容省略。。。 -->
</ BlogConfigurationSettings >

对于复杂类型的配置,其实并不限于采用序列化的手段来保存类的成员。也可以用手工分析 XML 里子节点的方式来手工创建设置类的实例。DNN 3.2.2 中就是这么做的。不过我个人觉得这个方式比起用序列化来说要麻烦一些。代码的复用性和抽象层次也不如 .Text 这种做法高。

四、sectionGroup

上面介绍了如何使用 section 来配置节点的处理方式。其实我们还可以用 sectionGroup,顾名思义 sectionGroup 就是一组 section 的组合,而且这个结构是可以任意嵌套的。这个的用法其实很简单,这里不再罗嗦。我作了一个嵌套的简单例子如下:
<? xml version="1.0" encoding="utf-8"  ?>
< configuration >
    
< configSections >
        
< sectionGroup  name ="neilSettings" >
            
< sectionGroup  name ="s1" >
                
< section  name ="s1a"  type ="System.Configuration.SingleTagSectionHandler"   />
                
< section  name ="s1b"  type ="System.Configuration.NameValueSectionHandler"   />
            
</ sectionGroup >
            
            
< section  name ="s2"  type ="System.Configuration.SingleTagSectionHandler"   />
        
</ sectionGroup >
            
    
</ configSections >
    
    
< neilSettings >
        
< s1 >
            
< s1a  m ="x" ></ s1a >
            
< s1b >
                
< add  key ="name"  value ="zhangsan"   />
                
< add  key ="age"  value ="20"   />
            
</ s1b >
        
</ s1 >
        
< s2  a ="1"  b ="2"  c ="3" ></ s2 >
    
</ neilSettings >
</ configuration >

要读取这个设置,我们这么写就可以了:
Hashtable s1a  =  (Hashtable) ConfigurationSettings.GetConfig( " neilSettings/s1/s1a " );
MessageBox.Show(s1a.Count.ToString()); 
//  1

NameValueCollection s1b 
=  (NameValueCollection) ConfigurationSettings.GetConfig( " neilSettings/s1/s1b " );
MessageBox.Show(s1b.Count.ToString()); 
//  2

Hashtable s2 
=  (Hashtable) ConfigurationSettings.GetConfig( " neilSettings/s2 " );
MessageBox.Show(s2.Count.ToString()); 
//  3

注意在 GetConfig 方法中,我们只要传入正确的 XPath 语法以找出所需节点就可以了。


除了用程序自身的 config 文件来存储配置,我们还可以自己来实现可读写的配置文件,存储复杂的设置。在这方面,ASP.NET 1.1 的 StarterKit 中有一个很好的实现,其主要原理是利用了强类型 DataSet 的一些功能。那样实现有一个好处,就是在 VS.NET 设计器里有很好的支持。可视化程度比较高。下一次我会详细来分析 ASP.NET 1.1 StarterKit 的配置实现原理。

你可能感兴趣的:(.net)