.NET設定檔解析過程詳解

來源:互聯網
上載者:User
過程|詳解

在我看來,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的時候發生過什麼了。



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.