過程|詳解
在我看來,WEB project的開發與WINFORM的開發最大的區別在於web的運行是在Framework上更高一層架構上運行,即ASP。NET架構,程式員在web下的開發可以說是黑盒開發,不是讓你去定義程式入口和執行順序,而是asp.net來調用你的各個方法,程式員做的一切都是一種受控的舞蹈。就像我們調用nunit之類的工具來測試一個dll一樣,nunit是容器,是架構,執行哪個方法是由nunt來決定的。因此,也就有了頁面執行循環各狀態等令剛入門的程式員困惑不已的事,其實,究其根源,在於不瞭解容器而去使用容器。對於asp.net架構的學習,我們不妨從設定檔開始。
對於程式開發人員而言,寫設定檔是經常性的工作,如果你寫了一個xx.config檔案,如果沒有詳盡的注釋,別人恐怕很難讀懂,沒有良好的配置架構,程式也失去了活力。在我看來,.net設定檔的特點在於反射定義和繼承性。
我們訪問設定檔時經常覺得設定檔的結構不太符合我們的需要,我們需要從裡面更方便地獲得自己定義的對象,而不僅僅是key和value,對於自訂設定檔的著述已有很多,在此不再描述,有興趣的朋友可以訪問
自訂配置節其實還是在.net設定檔架構的應用而已,我們先來搞懂設定檔的結構,弄清楚.net設定檔的運行方式。下面是machine.config的一部分內容:
<configSections>
<section name="runtime" type="System.Configuration.IgnoreSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowLocation="false" />
<sectionGroup name="system.net">
<section name="authenticationModules" type="System.Net.Configuration.NetAuthenticationModuleHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</ sectionGroup>
</configSections>
SDK中<section>的定義為:
<section
name="section name"
type="configuration section handler class, assembly"
allowDefinition="Everywhere|MachineOnly|MachineToApplication"
allowLocation="true|false" />
<sectionGroup>的定義為:
<sectionGroup
name="section group name"/>
</sectionGroup>
我們來看看.net架構內是如何利用這種結構的。反編譯System.dll找到GetConfig方法,在裡面我們發現實際擷取config的工作預設是由實現了IConfigurationSystem的DefaultConfiguationSystem類來實現的。
public static object GetConfig(string sectionName)
{
if (!ConfigurationSettings._configurationInitialized)
{
lock (typeof(ConfigurationSettings))
{
if ((ConfigurationSettings._configSystem == null) && !ConfigurationSettings.SetConfigurationSystemInProgress)
{
ConfigurationSettings.SetConfigurationSystem(new DefaultConfigurationSystem());
}
}
}
if (ConfigurationSettings._initError != null)
{
throw ConfigurationSettings._initError;
}
return ConfigurationSettings._configSystem.GetConfig(sectionName);
}
我們再來看DefaultConfigurationSystem,這個類主要包含了machine.config的名稱路徑的基本資料和一些uri操作,而實際的GetConfig的操作交給了ConfiguationRecord來處理,這個類沒有實現任何介面,可見他和DefaultConfiguration是綁定在一起的。
1internal class DefaultConfigurationSystem : IConfigurationSystem
2{
3 // Methods
4 internal DefaultConfigurationSystem();
5 object IConfigurationSystem.GetConfig(string configKey);
6 void IConfigurationSystem.Init();
7
8 // Properties
9 internal static Uri AppConfigPath { get; }
10 internal static string MachineConfigurationFilePath { get; }
11 internal static string MsCorLibDirectory { get; }
12
13 // Fields
14 private ConfigurationRecord _application;
15 private const string ConfigExtension = "config";
16 private const string MachineConfigFilename = "machine.config";
17 private const string MachineConfigSubdirectory = "Config";
18 private const int MaxPathSize = 0x400;
19}
20
事實上所有的設定檔的分析和擷取都是在ConfiguationRecord裡實現的,作為設定檔分析的第一步,正如我們經常做的一樣->載入一個設定檔,這個方法公開為 Load(filename)。DefaultConfiguationSystem的Init()方法中用machine.config建立了一個ConfiguationRecord對象,並將其作為父物件傳遞給當前程式的ConfiguationRecord對象,當然前提是當前程式有設定檔,比如myapp.config,然後再載入當前程式的設定檔,從而實現設定檔的繼承。
void IConfigurationSystem.Init()
{
lock (this)
{
if (this._application == null)
{
ConfigurationRecord record1 = null;
string text1 = DefaultConfigurationSystem.MachineConfigurationFilePath;
Uri uri1 = DefaultConfigurationSystem.AppConfigPath;
this._application = record1 = new ConfigurationRecord();
bool flag1 = record1.Load(text1);
if (!flag1 || (uri1 == null))
> {
return;
}
this._application = new ConfigurationRecord(record1);
this._application.Load(uri1.ToString());
}
}
}
現在我們可以專註於configuationrecord的具體實現了,load方法中得到一個xmltextwriter,並執行.scanfactoriesrecursive和scansectionsrecursive方法。
reader1 = ConfigurationRecord.OpenXmlTextReader(filename);
if (reader1 != null)
{
this.ScanFactoriesRecursive(reader1);
if (reader1.Depth == 1)
{
this.ScanSectionsRecursive(reader1, null);
}
return true;
}
scanfactoriesrecursive方法會調用他的一個重載方法來解析<configsections>中的<sectiongroup>,<section>,<remove>,<clear>,我們寫設定檔時大小寫可不能寫錯哦,.net沒有做toslower之類的轉換,直接就是 “== “。在這個方法裡程式會將解析得到的sectiongroup以key=tagkey,value= ConfigurationRecord.GroupSingleton的方式存到EnsureFactories裡,將section以key=tagkey,value=typestring的方式儲存,值得注意的是,這裡並沒有建立實現IConfigurationSectionHandler的執行個體對象,而是將其類型名(比如:字串”system.Configuration.NameValueSectionHandler”)作為值到EnsureFactories,等到後面GetConfig的時候再來反射建立。<remove>則存為ConfigurationRecord.RemovedFactorySingleton。<clear>就清空EnsureFactories。這裡的tagkey是各級name的組合,比如”mygroup/mysection”這樣以分隔字元”/”組合的形式。應該客觀地說這部分代碼用了很多goto語句,可讀性不是太好,但這樣寫也確實沒有什麼問題。
this.CheckRequiredAttribute(text3, "name", reader);
this.CheckRequiredAttribute(text4, "type", reader);
this.VerifySectionName(text3, reader);
string text5 = ConfigurationRecord.TagKey(configKey, text3);
if (this.HaveFactory(text5) != ConfigurationRecord.HaveFactoryEnum.NotFound)
{
objArray1 = new object[] { text3 } ;
throw this.BuildConfigError(SR.GetString("Tag_name_already_defined", objArray1), reader);
}
this.EnsureFactories[text5] = text4;
goto Label_02B6;
scansectionsrecursive方法會解析設定檔裡的section執行個體,並將其tagkey儲存到hashtable _unevaluatedSections中,表示尚未evaluated的configkey的集合,可見section執行個體對象的建立和handler一樣,都是fetch when need。在後面的操作Getconfig中會使用他。
if (this._unevaluatedSections == null)
{
this._unevaluatedSections = new Hashtable();
}
this._unevaluatedSections[text2] = null;
現在我們就可以看getconfig方法是怎麼來執行得到我們想要的對象的。
public object GetConfig(string configKey)
{
if (this._error != null)
{
throw this._error;
}
if (this._results.Contains(configKey))
{
return this._results[configKey];
}
object obj1 = this.ResolveConfig(configKey);
lock (this._results.SyncRoot)
{
this._results[configKey] = obj1;
}
return obj1;
}
如果_result中有對應configkey的section執行個體,就返回,沒有則需要對configkey進行resolveconfig,將解析到的對象儲存到_result中並返回給使用者。在resolveconfig方法中,可以看到如果當前的設定檔中沒有要求的configkey就會返回父級的section執行個體,比如machine.config裡的內容。
public object ResolveConfig(string configKey)
{
Hashtable hashtable1 = this._unevaluatedSections;
if ((hashtable1 != null) && hashtable1.Contains(configKey))
{
return this.Evaluate(configKey);
}
if (this._parent != null)
{
return this._parent.GetConfig(configKey);
}
return null;
}
然後就是evaluate及其後續操作了,主要就是將configkey分解成字串數組,一層層地去找對應的xmlelement,找到後傳給configkey對應的handler,如果該handler沒有建立則反射建立,然後由該handler建立section的執行個體對象,返回給使用者,該部分關鍵代碼如下:
ConfigXmlDocument document1 = new ConfigXmlDocument();
document1.LoadSingleElement(this._filename, reader);
config = factory.Create(config, null, document1.DocumentElement);
現在我們就明白了當我們用system..configurtion.configuationsetting.getconfig的時候發生過什麼了。