天天看點

揭開.NET 2.0配置之謎(三)

let's go on!

在我研究和實驗配置節的時候,我學到一些技巧,可是使他們更容易使用。自定義配置節的某些方面非常乏味,如所有時間一直通過調用<code>ConfigurationManager.GetSection("someSectionName")擷取SomeSectionClass。為了減輕繁瑣乏味,我嘗試在我的<code>ConfigurationSection</code>類中使用下列方式:</code>

public class SomeConfigurationSection  

{  

    static SomeConfigurationSection()  

    {  

        // Preparation...  

    }  

    // Properties...  

    #region GetSection Pattern  

    private static SomeConfigurationSection m_section;  

    /// &lt;summary&gt;  

    /// Gets the configuration section using the default element name.  

    /// &lt;/summary&gt;  

    public static SomeConfigurationSection GetSection()  

        return GetSection("someConfiguration");  

    /// Gets the configuration section using the specified element name.  

    public static SomeConfigurationSection GetSection(string definedName)  

        if (m_section == null)  

        {         

            m_section = ConfigurationManager.GetSection(definedName) as   

                        SomeConfigurationSection;  

            if (m_section == null)  

                throw new ConfigurationException("The &lt;" + definedName +   

                      "&gt; section is not defined in your .config file!");  

        }      

        return m_section;  

    #endregion  

上述的模式增加了一個靜态<code>GetSection()</code>方法給每個自定義<code>ConfigurationSection</code>類。它的一個重載方法,以一個字元串作為參數,允許你為.config中元素定義一個不同的名字,如果你喜歡的話。另外,預設的重載是可以使用的。這種模式使用在标準的應用程式(.exe)的配置節下工作的非常好。然而,如果配置節使用在一個web.config檔案中,你将需要使用下面的代碼:

using System.Web;  

using System.Web.Configuration;  

    /// &lt;remarks&gt;  

    /// If an HttpContext exists, uses the WebConfigurationManager  

    /// to get the configuration section from web.config.  

    /// &lt;/remarks&gt;  

        {  

            string cfgFileName = ".config";  

            if (HttpContext.Current == null)  

            {  

                m_section = ConfigurationManager.GetSection(definedName)   

                            as SomeConfigurationSection;  

            }  

            else 

                m_section = WebConfigurationManager.GetSection(definedName)   

                cfgFileName = "web.config";  

                  "&gt; section is not defined in your " +   

                  cfgFileName + " file!");  

正如你看到的,在ASP.NET下通路配置節需要使用<code>System.Web.Configuration.WebConfigurationManager</code>,而不是<code>System.Configuration.ConfigurationManager</code>。檢查目前的<code>HttpContext</code>足以确定使用哪個管理器(manager)去擷取配置節。還有一個增強,我們對這種模式,使得允許我們可以儲存修改。我們知道,必須使用<code>Configuration</code>對象來儲存修改,因為管理器(manager)類不提供<code>Save()</code>方法。我們可以在我們的GetSection方法中建立一個<code>Configuration</code>對象,但是最終将缺乏靈活性、效率不高。在最終完全的模式中,我這樣做,把<code>Configuration</code>對象作為一個參數:

    // Dictionary to store cached instances of the configuration object  

    private static Dictionary&lt;string,   

            SomeConfigurationSection&gt; m_sections;  

    /// Finds a cached section with the specified defined name.  

    private static SomeConfigurationSection   

            FindCachedSection(string definedName)  

        if (m_sections == null)  

            m_sections = new Dictionary&lt;string,   

                             SomeConfigurationSection&gt;();  

            return null;  

        }  

        SomeConfigurationSection section;  

        if (m_sections.TryGetValue(definedName, out section))  

            return section;  

        return null;  

    /// Adds the specified section to the cache under the defined name.  

    private static void AddCachedSection(string definedName,   

                   SomeConfigurationSection section)  

        if (m_sections != null)  

            m_sections.Add(definedName, section);  

    /// Removes a cached section with the specified defined name.  

    public static void RemoveCachedSection(string definedName)  

        m_sections.Remove(definedName);  

    /// to get the configuration section from web.config. This method  

    /// will cache the instance of this configuration section under the  

    /// specified defined name.  

        if (String.IsNullOrEmpty(definedName))  

            definedName = "someConfiguration";  

        SomeConfigurationSection section = FindCachedSection(definedName);  

        if (section == null)  

                section = ConfigurationManager.GetSection(definedName)   

                          as SomeConfigurationSection;  

                section = WebConfigurationManager.GetSection(definedName)   

            if (section == null)  

                   "&gt; section is not defined in your " + cfgFileName +   

                   " file!");  

            AddCachedSection(definedName, section);  

        return section;  

    /// Gets the configuration section using the default element name   

    /// from the specified Configuration object.  

    public static SomeConfigurationSection GetSection(Configuration config)  

        return GetSection(config, "someConfiguration");  

    /// Gets the configuration section using the specified element name   

    public static SomeConfigurationSection GetSection(Configuration config,   

                                           string definedName)  

        if (config == null)  

            throw new ArgumentNullException("config",   

                  "The Configuration object can not be null.");  

        SomeConfigurationSection section = config.GetSection(definedName)   

                                           as SomeConfigurationSection;  

            throw new ConfigurationException("The &lt;" + definedName +   

                  "&gt; section is not defined in your .config file!");  

通過傳遞<code>Configuration</code>對象,一個可儲存的配置節執行個體能在XML檔案中檢索一個指定名字的配置節。這把我帶到另外一個重要的配置節秘訣。配置節元素的名字不一定必須的固定不變,也不一定隻有一個配置節的執行個體。在一個.config檔案按中每個配置節可以定義和設定多次,隻要給每個執行個體不同的名字:

&lt;configuration&gt; 

  &lt;configSections&gt; 

    &lt;section name="example1" type="Examples.Configuration.ExampleSection,  

                                      Examples.Configuration" /&gt; 

    &lt;section name="example2" type="Examples.Configuration.ExampleSection,   

    &lt;section name="example3" type="Examples.Configuration.ExampleSection,   

  &lt;/configSections&gt; 

  &lt;example1 /&gt; 

  &lt;example2 /&gt; 

  &lt;example3 /&gt; 

&lt;/configuration&gt; 

以同樣的方式配置節組也可以定義多次。這使得一個通常的自定義配置結構在同一個應用程式中,同時以多種方式使用,而且使得自定義配置可以重用。因為可能在一個.config檔案中一個配置節定義多次,最終實作上述的模式包括一個簡單的執行個體緩存。每次用不同的<code>definedName</code>調用<code>GetSection(string)</code>,将傳回不同的配置節對象且存儲在緩存中。連續以相同的名字調用将傳回相同的緩存執行個體。這種模式的另一個重要方面是缺少為兩個新版本的<code>GetSection</code>(以一個<code>Configuration</code>對象作為參數的<code>GetSection</code>方法)的緩存。用<code>Configuration</code>對象比用<code>ConfigurationManager</code>或WebConfigurationManager花費更大的開銷。通過配置管理器(manager)調用<code>GetSection()</code>方法将完全緩存節,而通過<code>Configuration</code>對象調用将導緻節每次都要被解析。一般來說,<code>Configuration</code>對象隻有當需要儲存配置更改是才使用。配置管理器類應該被用來通路讀取配置。這将保證使用配置設定時性能最佳。

最後一個秘訣是關于性能的主題。除非你實作進階的配置節或元素,包括事件通知,緩存配置設定在變量中通常沮喪的(行不通的)。更多關于這個的讨論将在下面的進階部分,首先考慮以下非常簡單的情景:

public class SomeProgram  

    static Main()  

        s_config = MyConfig.GetSection();  

        s_duration = s_config.Duration;  

        s_timer = new Timer(  

            new TimerCallback(),  

            null,  

            TimeSpan.Zero  

            s_duration  

        );  

        Console.ReadKey();  

        s_timer.Dispose();  

    private static MyConfig s_config;  

    private static Timer s_timer;  

    private static TimeSpan s_duration;  

    private static void WriteCurrentTime(object data)  

        Console.WriteLine("The current time is " + DateTime.Now.ToString());  

        if (s_duration != s_config.Duration)  

            s_duration = s_config.Duration;  

            s_timer.Change(TimeSpan.Zero, s_duration);  

在上面的應用程式中,我們希望如果配置檔案更新的話,改變定時器間隔。我們配置節的執行個體,s_config,将一直保持更新。是以如果在應用程式運作時,.config檔案改變,任何改變将被發現并載入到記憶體中。如果你跟我在文章中一樣實作你的配置節,覆寫靜态構造器和替換屬性(properties)集合,這樣你的集合将有有高的性能。這使得通路一個配置屬性(property)相對廉價的操作,是以上述代碼可以重寫成如下:

            s_config.Duration  

        Console.WriteLine("The current time is " +   

                          DateTime.Now.ToString());         

        s_timer.Change(TimeSpan.Zero, s_config.Duration);  

如果這個例子過于簡單揭示直接使用配置設定的意義,那麼想象一個更複雜的場景,一個配置值在一個緩存變量。變量是順序通過一個鍊來調用,然後循環使用。如果想要的結果對任何配置的過期及時發現并回答,那麼緩存将不起作用。你必須直接通路配置屬性(property),不用把它緩存到變量中。配置設定是全局通路的,可以在應用程式的任何地方。這意味着在你的代碼中的任何地方都可以通路配置屬性(property),而不用緩存變量的值和傳遞一個變量參數。将使得代碼更幹淨,因為你要求更少的的參數。如果高性能是絕對必要的,是有可能寫一個有事件的配置節,當配置資料在磁盤上已經改變能通知使用者。然而,這是一個更進階的主題,将在以後讨論。

本文中概述的資訊提供了一個全面地介紹.NET 2.0架構的配置功能特性。然而,這決不是一個全面的檔案,并且還有一些更複雜的使用配置節。其他資訊将在後面的文章:

解碼.NET 2.0配置之謎

破解.NET 2.0配置之謎

12.1、附錄A: 配置結構的級聯

在ASP.NET應用程式中,web.config檔案可能針對任何IIS“應用程式”。倘若應用程式的虛拟檔案夾是另一個應用程式的孩子,來自父應用程式的web.config檔案将和子應用程式的web.config合并。因為IIS中的應用程式可以嵌套任何級别的深度,當子應用應程式的web.config加載時,配置級聯将産生。

假設我們有一個站點安裝在IIS裡,以下面的層次結構且每個web.config檔案包含一個共同的集合:

\wwwroot  

      web.config  

      \firstapp  

          web.config  

      \anotherapp  

          \childapp  

              web.config  

      \finalapp  

&lt;!-- \wwwroot\web.config --&gt; 

    &lt;commonCollection&gt; 

        &lt;add key="first"  value="C98E4F32123A" /&gt; 

        &lt;add key="second" value="DD0275C8EA1B" /&gt; 

        &lt;add key="third"  value="629B59A001FC" /&gt; 

    &lt;/commonCollection&gt; 

&lt;!-- \wwroot\firstapp\web.config --&gt; 

        &lt;remove key="first" /&gt;          

        &lt;add key="first"  value="FB54CD34AA92" /&gt; 

        &lt;add key="fourth" value="DE67F90ACC3C" /&gt; 

&lt;!-- \wwroot\anotherapp\web.config --&gt; 

        &lt;add key="fourth" value="123ABC456DEF" /&gt; 

        &lt;add key="fifth"  value="ABC123DEF456" /&gt; 

        &lt;add key="sixth"  value="0F9E8D7C6B5A" /&gt; 

&lt;!-- \wwroot\anotherapp\childapp\web.config --&gt; 

        &lt;remove key="second" /&gt; 

        &lt;remove key="fourth" /&gt; 

        &lt;remove key="sixth" /&gt; 

        &lt;add key="seventh" value="ABC123DEF456" /&gt; 

        &lt;add key="ninth"  value="0F9E8D7C6B5A" /&gt; 

&lt;!-- \wwroot\lastapp\web.config --&gt; 

        &lt;clear /&gt; 

        &lt;add key="first"  value="AABBCCDDEEFF" /&gt; 

        &lt;add key="second" value="112233445566" /&gt; 

        &lt;add key="third"  value="778899000000" /&gt; 

        &lt;add key="fourth" value="0A0B0C0D0E0F" /&gt; 

如果我們研究了每個應用程式的集合,結果将如下:

\wwwroot\web.config

first = C98E4F32123A

second = DD0275C8EA1B

third = 629B59A001FC

\wwwroot\firstapp\web.config

first = FB54CD34AA92

fourth = DE67F90ACC3C

\wwwroot\anotherapp\web.config

fourth = 123ABC456DEF

fifth = ABC123DEF456

sixth = 0F9E8D7C6B5A

\wwwroot\anotherapp\childapp\web.config

seventh = ABC123DEF456

ninth = 0F9E8D7C6B5A

\wwwroot\lastapp\web.config

first = AABBCCDDEEFF

second = 112233445566

third = 778899000000

fourth = 0A0B0C0D0E0F

我希望這個簡單的示例,子應用程式的web.config是如何繼承設定的,這些設定是如何被覆寫,足夠了。你可能不是經常遇到這種情況,但是了解發生了什麼,以及如何覆寫父web.config的配置設定,應該有助于減輕ASP.NET開發者的配置檔案問題。

12.2、附錄B: 包含外部配置檔案

盡管在.NET 2.0的配置功能中都很偉大,但是仍有一個缺點。當工作在一個多環境的單一項目中,管理配置檔案是一個噩夢。管理多環境下的多版本的配置檔案(如開發、測試、階段、産品)的過程,我目前的工作包括手工比較.config檔案,将更改部署到一個環境或另外一個,通過手工合并。我花了幾個月試圖找到一種更好的方法,最終找到了。進入這樣那樣一些沒有“沒有文檔的”或很少文檔的——微軟著名的特點,的其中的一個:configSource。當我用Reflector深入挖掘.NET 2.0配置源碼的時候,碰到這個珍品,美妙的小工具。

每個配置節在被.NET配置類解析和加載時,都配置設定了一個<code>SectionInformation</code>對象。<code>SectionInformation</code>對象包含關于配置節的元資訊,并允許管理節如何互相覆寫,當定義在一個子web.config中時(ASP.NET)。現在,我們将忽略大部分<code>SectionInformation</code>對象提供的,考慮configSource屬性(property)。通過添加configSource屬性(attribute)到任何<code>ConfigurationSection的</code>根元素,你可以指定一個備用,外部的配置設定将被加載。

&lt;configuration&gt;  

  &lt;connectionStrings configSource="externalConfig/connectionStrings.config"/&gt;  

&lt;/configuration&gt;  

&lt;!-- externalConfig/connectionStrings.config --&gt;  

&lt;connectionStrings&gt;  

  &lt;add name="conn" connectionString="blahblah" /&gt;  

&lt;/connectionStrings&gt; 

在上面的配置檔案中,<code>&lt;connectionStrings&gt;</code>節源于名為externalConfig/connectionStrings.config的檔案。所有應用程式的連接配接字元串将加載自這個特定檔案。現在,連接配接字元串是從外部資源加載的,在相對相同位置的每個環境,他相對簡單地建立一個connectionStrings.config檔案。是以externalConfig/   connectionStrings.config 檔案的路徑。這裡漂亮的地方是,我們可以正确地為每個環境定義連接配接字元串定義一次。我們不用擔心意外覆寫那些設定,在部署一個config檔案時,無論是否合并得當或根本不合并。這是一個巨大的福音,當更改一個應用程式到産品環境是,他的關鍵正确的資料庫連接配接字元串存在。使用configSource屬性(attribute)失效,就是它要求所有的配置節将放置在外部檔案中。沒有繼承或覆寫是可能的,在某些情況下使它沒用。所有的外部配置檔案用configSource屬性(attribute)引用,也必須放在相對子到主的.config檔案路徑上。我相信這是考慮web環境中的安全性,存儲檔案在相對父路徑上。

别的需要注意的是<code>&lt;appSettings&gt;</code>節有一個更好的選擇使用configSource,稱為<code>file</code>。如果你使用<code>file</code>屬性(attribute)而不是configSource在<code>&lt;appSettings&gt;</code>節裡,你可以定義設定在根.config檔案或引用檔案都可以。根.config檔案的設定也能被引用檔案覆寫,簡單地用相同的鍵添加東西。可悲的是,<code>file</code>屬性(attribute)隻适用在<code>&lt;appSettings&gt;</code>節,而不是建立在配置架構下。在我們自己的配置節中也可能實作類似的屬性(attribute)。這将在将來的進階配置主題部分讨論,幾個先決部分之後。

請繼續關注!

     本文轉自Saylor87 51CTO部落格,原文連結:http://blog.51cto.com/skynet/365552,如需轉載請自行聯系原作者

繼續閱讀