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

本文详细介绍了.NET配置文件的各种高级用法,包括appSettings的基本使用、clear与remove元素的作用、自定义节处理器(SectionHandlers)及其类型,以及sectionGroup的应用。

 

 

.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 的配置实现原理。

 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值