引言
現代軟件開發中,各種技術、技巧越來越依賴配置,譬如客戶端對用戶體驗的個性化設置、系統的各種運行時參數設置、可插拔的插件機制、基于配置的ioc架構模式等。配置方式也從最初的二進制存儲格式逐步過度到ini文本格式直至今時所廣泛使用的xml格式。使用xml格式進行配置,大大提高了對設置數據的表現能力,但是在 .net 1.x 中對xml配置的操控還有諸多不便,尤其是對xml配置的存儲同步機制很不完善,而從 .net 2.0 開始,框架提供了更豐富和易于操控使用的機制。
.net 中的配置文件(xml)必須以“<configuration>”為根節點,配置文件分為兩大部分:配置聲明區和數據設置區。
配置聲明區:位于<configuration><configsections>內,通過<section>節點進行聲明定義。
數據設置區:位于<configuration>根節點內除<configsections>以外的任意節點。
數據設置區可以是用戶定義的任意結構層次,但是其“根節點”必須預先在設置聲明區定義,運行時會進行有效性檢測,一旦發現沒有聲明的配置節點則會產生一個運行時配置異常。
范例配置文件
<configuration>
<configsections>
<section name="datasystems" type="swsystem.data.configuration.datasystemssection, swsystem.data" />
</configsections>
<datasystems>
<datasystem name="imrp" currentprovider="sqlprovider">
<dataprovider name="mssqlprovider" type="swsystem.data.providers.sqldataprovider" datafile="d:/zongsoft/develop 2005/swsystem.data/services/swdataengine.xml" connectionstring="uid=sa;pwd=;initial catalog=imrp;data source=127.0.0.1" />
<dataprovider name="postgresqlprovider" type="swsystem.data.providers.postgredataprovider" datafile="d:/zongsoft/develop 2005/swsystem.data/services/swdataengine.xml" connectionstring="server=127.0.0.1;port=5432;user id=postgres;password=postgres;database=imrp;encoding=unicode;" />
<datamodules>
<add name="stockmodule" type="zongsoft.applications.imrp.business.stockmodule, zongsoft.applications.imrp.business" />
</datamodules>
</datasystem>
</datasystems>
</configuration>
概述
在.net 2.0中實現自定義配置,可以使用編程或聲明性(屬性化)代碼編寫模型創建自定義配置節。
編程模型。此模型要求為每個節屬性 (attribute) 創建一個用于獲取和/或設置其值的屬性 (property),并將其添加到基礎 configurationelement 基類的內部屬性 (property) 包中。
聲明模型。這種比較簡單的模型也稱為屬性 (attribute) 化模型,允許您通過使用屬性 (property) 來定義節屬性 (attribute),并使用屬性 (attribute) 對其進行修飾。
配置文件中的元素稱為 基本xml元素 或 節。基本元素只是具有相關屬性(如果有)的簡單 xml 標記。節最簡單的形式與基本元素一致。而復雜的節可以包括一個或多個基本元素、元素的集合以及其他節。
configurationproperty 類表示配置節點中的特性(attribute)或它的子元素,我們要做的就是通過 configurationproperty 類將配置實體類中的屬性(property)映射到對應的節點特性(attribute)。
配置聲明
要在配置文件中使用自定義的配置數據,必須在聲明區通過<section>節點對自定義配置節處理程序進行聲明,該節點有兩個必需屬性:name 和 type。
name 屬性:指定與 type 屬性中指定的配置節處理程序關聯的配置節或元素的名稱。這是該元素在配置文件的設置區中使用的名稱。
type 屬性:指定用來處理 name 屬性所指定的設置節的處理程序。使用如下格式:
type="configurationsectionhandlerclass, assemblyfilename, version, culture, publickeytoken"
如果對處理程序的版本沒要求,或者其為無區域和強簽名的程序集,則可以省略后面的三項,否則必須嚴格匹配版本。注意:程序集文件名不要帶其擴展文件名(通常為.dll)。
范例配置文件中的配置聲明定義如: <section name="datasystems" type="swsystem.data.configuration.datasystemssection, swsystem.data" /> 則表示數據設置區中的<datasystems>節點由 swsystem.data.configuration.datasystemssection 類進行處理,并且該類位于 swsystem.data 程序集中。
在 .net 1.x 中要實現自定義的配置處理程序類,其必須實現 iconfigurationsectionhandler 接口,現在 .net 2.0 中只需要將你的處理程序類繼承自 configurationsection 類即可。其處理步驟大致如下:
首先定義并創建一個 configurationpropertycollection 類的實例,用以保存配置節點的特性(attribute)以及它的所有子元素映射,當然你也可以使用 configurationelement 基類中的 properties 屬性。
然后創建 configurationproperty 類以映射到對應的節點特性(attribute)或子元素。
在類型構造函數或實例構造函數中,將創建的 configurationproperty 類實例加入到已創建好的 configurationpropertycollection 集合中。
最終,我們的范例配置處理程序類看起來可能是這樣(如果你使用聲明模式則代碼看起來沒有這么麻煩,這些配置屬性類將由框架運行時幫你反射生成,正因為如此,所以它的運行時效率要差些。):
public class datasystemssection : configurationsection
{
private static readonly configurationproperty _datasystems = new configurationproperty(null, typeof(datasystemelementcollection), null, configurationpropertyoptions.isdefaultcollection);
private static configurationpropertycollection _properties = new configurationpropertycollection();
static datasystemssection()
{
_properties.add(_datasystems);
}
public datasystemelementcollection datasystems
{
get
{
return (datasystemelementcollection)base[_datasystems];
}
}
protected override configurationpropertycollection properties
{
get
{
return _properties;
}
}
}
節點映射/配置實體
為所有的配置節點創建對應的配置實體類,該類中的屬性(property)對應節點中的特性(attribute)和子元素(集合)。其編寫步驟和開發方式與處理程序類似,只是這時我們的基類變成了 configurationelement。
public class datasystemelement : configurationelement
{
private static readonly configurationproperty _name = new configurationproperty("name", typeof(string), null, null, new stringvalidator(1), configurationpropertyoptions.iskey | configurationpropertyoptions.isrequired);
private static readonly configurationproperty _currentprovider = new configurationproperty("currentprovider", typeof(string), string.empty, configurationpropertyoptions.isrequired);
private static readonly configurationproperty _datamodules = new configurationproperty("datamodules", typeof(datamoduleelementcollection), null, configurationpropertyoptions.none);
private static readonly configurationproperty _dataproviders = new configurationproperty(null, typeof(dataproviderelementcollection), null, configurationpropertyoptions.isrequired | configurationpropertyoptions.isdefaultcollection);
private static configurationpropertycollection _properties = new configurationpropertycollection();
#region 類型構造函數
static datasystemelement()
{
_properties.add(_name);
_properties.add(_currentprovider);
_properties.add(_dataproviders);
_properties.add(_datamodules);
}
#endregion
#region 構造函數
public datasystemelement()
{}
public datasystemelement(string name)
{
this.name = name;
}
public datasystemelement(string name, string currentprovider)
{
this.name = name;
this.currentprovidername = currentprovider;
}
#endregion
#region 公共屬性
public string name
{
get
{
return (string)base[_name];
}
set
{
base[_name] = value;
}
}
public string currentprovidername
{
get
{
return (string)this[_currentprovider];
}
set
{
this[_currentprovider] = value;
}
}
public datamoduleelementcollection datamodules
{
get
{
return (datamoduleelementcollection)base[_datamodules];
}
}
public dataproviderelementcollection dataproviders
{
get
{
return (dataproviderelementcollection)base[_dataproviders];
}
}
#endregion
}
需要注意的是,<dataprovider> 和 <datamodules> 子元素處理方式的差異。
<datamodules> 是個嵌套的集合節點,它下面有標準的 <add>, <remove>, <clear> 子元素,且 <datamodules> 元素不能在同一個 <datasystem> 節點下出現多次。
<dataprovider> 是 <datasystem> 節點下的直接子節點集合,<dataprovider> 是可以在同一個父節點下出現多次的集合項節點,故需要對其映射的 configurationproperty 類構造函數中的 options 參數包含 configurationpropertyoptions.isdefaultcollection 枚舉項(它指示.net框架構造一個嵌套節),另外,不能指定它對應的配置節點的名字,即必須保持構造函數中 name 參數的值為空引用([c#]null/[vb.net]nothing)或空字符串(string.empty/"")。 節點集合
創建派生自 configurationelementcollection 的類,在派生類中必須重寫(override)的抽象方法:
protected abstract configurationelement createnewelement()
在從配置文件加載集合時,會調用該方法以創建各個元素。重寫該方法以創建特定類型的自定義 configurationelement 對象。
protected abstract object getelementkey(configurationelement element)
在派生類中重寫時獲取指定配置元素的鍵值。
對于非默認的節點集合類,還必須重寫 collectiontype 和 elementname 只讀屬性的getter,其代碼可能如下:
protected override string elementname
{
get
{
return "dataprovider"; //"dataprovider" replace with your elementname in collection.
}
}
public override configurationelementcollectiontype collectiontype
{
get
{
return configurationelementcollectiontype.basicmap;
}
}
可以重寫 throwonduplicate 只讀屬性的getter,以指示當向 configurationelementcollection 添加重復的 configurationelement 是否會導致引發異常。默認情況,只有當該元素的 collectiontype 值為 addremoveclearmap 或 addremoveclearmapalternate 時才會引發異常,如果希望非默認節點集合不接受重復項(通常如此),那么就必須重寫該屬性的getter,始終返回真(true)。
請注意,鍵和值都相同的元素不會被視為重復元素,而是接受此類元素且不出現提示,只有鍵相同而值不同的元素才被視為是重復元素。原因是這些元素不會進行競爭。但是,無法添加鍵相同而值不同的元素,因為無法從邏輯上確定哪個競爭值有效。
索引器的設計模式
通常需要定義索引器的兩個重載(overloads),一個接受整型數下標的可讀寫的索引器;一個是接受字符串鍵值的只讀索引器。
public dataproviderelement this[int index]
{
get
{
return (dataproviderelement)baseget(index);
}
set
{
if(baseget(index) != null)
baseremoveat(index);
baseadd(index, value);
}
}
public new dataproviderelement this[string name]
{
get
{
return (dataproviderelement)baseget(name);
}
}
其他事項
通常還需要公開一些對集合操作的方法,大致如下:
public int indexof(dataproviderelement value)
{
return baseindexof(value);
}
public void add(dataproviderelement value)
{
baseadd(value);
}
public void remove(dataproviderelement value)
{
if(baseindexof(value) >= 0)
baseremove(value.name);
}
public void remove(string name)
{
baseremove(name);
}
public void removeat(int index)
{
baseremoveat(index);
}
public void clear()
{
baseclear();
}
總結
這只是我想要撰寫的有關 .net 2.0 中的基礎技術篇文章中的第一篇。在這篇文章中,我介紹了有關配置方面的一些基本概念,并闡述了在 .net 2.0 中如何定制你自己的配置處理程序,以及這過程中需要注意的一些細節問題。您已經看到,通過對 .net 2.0 中提供的基礎架構的擴展,我們可以很容易完成特性化的配置定制。