首页 > 编程 > .NET > 正文

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

2024-07-10 13:11:25
字体:
来源:转载
供稿:网友

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



发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表