在 .net 1.1 中,我們都知道可以使用 app.config 或者 web.config (asp.net) 來(lái)保存一些設(shè)置。可是對(duì)于大多數(shù)人來(lái)說(shuō),可能用的最多的只是把它當(dāng)作一個(gè)簡(jiǎn)單的 ini 文件來(lái)存儲(chǔ) key-value 鍵值對(duì),比如數(shù)據(jù)庫(kù)鏈接字符串,上傳文件路徑之類(lèi)的。但是實(shí)際上配置文件里可以存放任意復(fù)雜的結(jié)構(gòu)。如果讀過(guò) dnn,.text 之類(lèi)程序的代碼,就可以找到這些應(yīng)用的范例。不過(guò)這些項(xiàng)目的代碼一般都比較繁雜,因此這里我結(jié)合 .text 的配置方法,對(duì)配置文件的用法來(lái)做一個(gè)簡(jiǎn)單的小結(jié)。
一、最簡(jiǎn)單的寫(xiě)法,只用到 appsettings 元素。
appsettings 里的設(shè)定在 configurationsettings 類(lèi)里有默認(rèn)的屬性來(lái)訪問(wèn),他返回的是一個(gè) namevaluecollection 子類(lèi)的實(shí)例。所以通常簡(jiǎn)單的字符串值可以保存在這里。寫(xiě)法如下:
<? xml version="1.0" encoding="utf-8" ?>
< configuration >
<!-- 最簡(jiǎn)單的,在 appsettings 里面寫(xiě) -->
< appsettings >
<!-- 定義兩個(gè)鍵值 -->
< add key ="key1" value ="123" />
< add key ="key2" value ="456" />
</ appsettings >
</ configuration >
讀取的代碼:
string key1 = configurationsettings.appsettings["key1"];
string key2 = configurationsettings.appsettings["key2"];
二、稍微加點(diǎn)料。。
appsettings 中不僅僅可以用 add 來(lái)添加鍵值,還可以用 clear 或 remove 元素。
clear 的意思是,去除父層次的配置文件中定義的所有鍵值。
所謂“父層次”的意思是,比如我們?cè)?asp.net 中,當(dāng)我們用 configurationsettings.appsettings[key] 去讀取一個(gè)值的時(shí)候,首先會(huì)去檢查 machine.config 里是否有此鍵值的配置,然后再去讀取 web.config. 另外,如果在不同的目錄層次中配置 web.config,則子目錄中 web.config 的配置會(huì)覆蓋父目錄中的設(shè)置。那么這里 machine.config 相對(duì)于當(dāng)前的 web.config, 或者父目錄的 config 文件相對(duì)于子目錄的 config 文件,就是一個(gè)父子層次的關(guān)系。
remove 則可以移除一個(gè)父層次中設(shè)定的鍵值。
加入這兩種語(yǔ)法后的配置文件如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- 最簡(jiǎn)單的,在 appsettings 里面寫(xiě) -->
<appsettings>
<!-- 這個(gè)命令可以刪除更高層次中已經(jīng)定義的所有設(shè)置 -->
<clear />
<!-- 這個(gè)命令刪除一個(gè)設(shè)置 -->
<remove key="somekey" />
<!-- 添加設(shè)置 -->
<add key="key1" value="123" />
<add key="key2" value="456" />
</appsettings>
</configuration>
(注:remove 和 clear 同樣適用于下面將要提到的 section 和 sectiongroup 定義的元素當(dāng)中可以用 add 的地方,不再一一闡述)
三、節(jié)處理器 (section handlers)
在配置文件里除了 appsettings, 還可以自已寫(xiě) xml 格式的配置元素,這些元素叫做節(jié)(section)。當(dāng)然,如果你自己寫(xiě)一堆復(fù)雜的 xml 格式的標(biāo)簽,.net 自身是不知道如何解析的,因此這里就需要你在指定節(jié)的同時(shí),告訴 .net 如何處理它們,也就是定義“節(jié)處理器”(section handlers)。
每一個(gè)自定義的節(jié),都需要在 configsections 下面定義它們的節(jié)處理器。先來(lái)看一個(gè)例子:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- 這個(gè)里面用來(lái)定義節(jié)處理器 -->
<configsections>
<section name="dicvalues" type="system.configuration.dictionarysectionhandler" />
</configsections>
<!-- 這是一個(gè)自定義的節(jié) -->
<dicvalues>
<add key="key1" value="abc" />
<add key="key2" value="def" />
</dicvalues>
</configuration>
這里定義的節(jié)使用的是 .net framework 里已有的一個(gè)類(lèi): dictionarysectionhandler.
因?yàn)檫@些自定義的 sectionhandler 都要提供給 configurationsettings 類(lèi)使用,因此它們都要實(shí)現(xiàn) iconfigurationsectionhandler 接口。(具體原因可以用 reflector 查看 configurationsettings 的 getconfig 方法,一路追蹤下去即可找到答案)。
對(duì)于一些常見(jiàn)形式的數(shù)據(jù),系統(tǒng)內(nèi)部定義了幾種 handler, 其用法詳細(xì)敘述如下:
1. dictionarysectionhandler
這個(gè)類(lèi)型的 handler 的 getconfig 方法返回一個(gè) hashtable 類(lèi)型的對(duì)象。配置方法見(jiàn)上面一個(gè) xml . 我們可以這樣寫(xiě)代碼來(lái)訪問(wèn)其中的設(shè)定:
object o = configurationsettings.getconfig("dicvalues");
hashtable ht = (hashtable) o;
foreach (string key in ht.keys)
{
messagebox.show(key + " = " + ht[key]);
}
2. namevaluesectionhandler
config 文件里設(shè)定的方法跟 dictionarysectionhandler 類(lèi)似:
<?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 方法返回的是一個(gè) namevaluecollection 對(duì)象:
namevaluecollection c = (namevaluecollection) configurationsettings.getconfig("namevalues");
foreach (string key in c.keys)
{
messagebox.show(key + " = " + c[key]);
}
3. singletagsectionhandler
這種類(lèi)型的元素表現(xiàn)為一個(gè)簡(jiǎn)單元素,只有屬性而沒(méi)有子節(jié)點(diǎn)。各個(gè)屬性的值,將會(huì)在讀取時(shí)存到一個(gè) 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
有時(shí)候需要定義一些元素,不準(zhǔn)備由 configurationsettings 類(lèi)來(lái)處理,而是在外部處理。這時(shí)候?yàn)榱吮苊猱a(chǎn)生異常,用這個(gè) handler 來(lái)聲明,可以讓 configurationsettings 類(lèi)讀取的時(shí)候忽略該元素。這個(gè)用得比較少。
5. 自定義節(jié)處理器
通過(guò)實(shí)現(xiàn) iconfigurationsectionhandler 接口,我們可以實(shí)現(xiàn)自己的 sectionhandler,在其中保存復(fù)雜的設(shè)定信息。最常見(jiàn)的是結(jié)合序列化來(lái)使用。
比如我們需要在配置文件里保存如下信息:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- 配置節(jié)定義部分 -->
<configsections>
<!-- 指定一個(gè)叫做 studentsettings 的元素,其處理程序是 configdemos.config1.studentsettingssectionhandler.
注意:這些程序都必須繼承自 iconfigurationsectionhandler 接口。
這個(gè)元素還可以有兩個(gè)屬性:allowdefinition, allowlocation,其含義自己看 msdn.
-->
<section name="studentsettings" type="config1.studentsettingssectionhandler, config1" />
</configsections>
<!-- 實(shí)際的數(shù)據(jù)部分 -->
<studentsettings>
<students>
<student>
<name>張三</name>
<age>20</age>
</student>
<student>
<name>李四</name>
<age>30</age>
</student>
</students>
</studentsettings>
</configuration>
我們要在其中保存一組學(xué)生的信息,每一個(gè)學(xué)生有名字,年齡等信息。首先我們實(shí)現(xiàn)學(xué)生類(lèi),以及相應(yīng)的 settings 類(lèi):
namespace config1
{
using system;
using system.xml.serialization;
[serializable]
public class student
{
private string name;
private int age;
// 表示要將此屬性序列化為一個(gè)元素,而不是屬性
[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;
// 這個(gè) attribute 指示該屬性序列化為 xml 的時(shí)候,以多個(gè)子元素的形式表現(xiàn)
[xmlarray("students")]
public student[] students
{
get { return students; }
set { students = value; }
}
}
}
接著我們實(shí)現(xiàn)一個(gè)節(jié)處理器如下,這個(gè)類(lèi)名字和 config 里定義的是對(duì)應(yīng)的:
namespace config1
{
using system.configuration;
using system.xml;
using system.xml.serialization;
public class studentsettingssectionhandler : iconfigurationsectionhandler
{
#region iconfigurationsectionhandler 接口的實(shí)現(xiàn)
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
}
}
好了,我們現(xiàn)在可以用下面的代碼來(lái)讀取設(shè)置了。
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);
}
以上的實(shí)現(xiàn)雖然比較可行,但是考慮到序列化是一個(gè)很普遍的操作,而我們?cè)?studentsettingssectionhandler 類(lèi)的 create 方法里,寫(xiě)死了 studentsettings 這個(gè)類(lèi)型。這里顯然有一種不能重用的 bad smell,比如我現(xiàn)在需要序列化另一個(gè)設(shè)定類(lèi)型的實(shí)例,豈不是又要重新寫(xiě)一個(gè)這樣的類(lèi)?
解決這個(gè)的辦法是讓設(shè)置類(lèi)的類(lèi)型變得可以配置,這個(gè)其實(shí)在 .text 中已經(jīng)有了一個(gè)很好的實(shí)現(xiàn)了,看一下代碼:
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));
}
}
}
這個(gè)代碼里讀取了當(dāng)前節(jié)點(diǎn)的 type 屬性,用反射的方式來(lái)創(chuàng)建類(lèi)型。相應(yīng)的配置文件里這樣寫(xiě)就可以了:
<blogconfigurationsettings type="dottext.framework.configuration.blogconfigurationsettings, dottext.framework">
<!-- 內(nèi)容省略。。。-->
</blogconfigurationsettings>
對(duì)于復(fù)雜類(lèi)型的配置,其實(shí)并不限于采用序列化的手段來(lái)保存類(lèi)的成員。也可以用手工分析 xml 里子節(jié)點(diǎn)的方式來(lái)手工創(chuàng)建設(shè)置類(lèi)的實(shí)例。dnn 3.2.2 中就是這么做的。不過(guò)我個(gè)人覺(jué)得這個(gè)方式比起用序列化來(lái)說(shuō)要麻煩一些。代碼的復(fù)用性和抽象層次也不如 .text 這種做法高。
四、sectiongroup
上面介紹了如何使用 section 來(lái)配置節(jié)點(diǎn)的處理方式。其實(shí)我們還可以用 sectiongroup,顧名思義 sectiongroup 就是一組 section 的組合,而且這個(gè)結(jié)構(gòu)是可以任意嵌套的。這個(gè)的用法其實(shí)很簡(jiǎn)單,這里不再羅嗦。我作了一個(gè)嵌套的簡(jiǎn)單例子如下:
<?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>
要讀取這個(gè)設(shè)置,我們這么寫(xiě)就可以了:
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 語(yǔ)法以找出所需節(jié)點(diǎn)就可以了。
除了用程序自身的 config 文件來(lái)存儲(chǔ)配置,我們還可以自己來(lái)實(shí)現(xiàn)可讀寫(xiě)的配置文件,存儲(chǔ)復(fù)雜的設(shè)置。在這方面,asp.net 1.1 的 starterkit 中有一個(gè)很好的實(shí)現(xiàn),其主要原理是利用了強(qiáng)類(lèi)型 dataset 的一些功能。那樣實(shí)現(xiàn)有一個(gè)好處,就是在 vs.net 設(shè)計(jì)器里有很好的支持。可視化程度比較高。下一次我會(huì)詳細(xì)來(lái)分析 asp.net 1.1 starterkit 的配置實(shí)現(xiàn)原理。
新聞熱點(diǎn)
疑難解答
圖片精選