C# - App.config自訂section程式碼架構Part I(基本用法)

.Net本身有系統參數設置機制(Configuration System),其加入的外部設定檔稱為Application Configuration File。光看全名會不懂是什麼東西,其實就是.Net App(ASP.NET, .Net Console, Window Form App)的Web.config, App.configSection為設定區間的最小單位。因為Application Configuration File是.Net Configuration System提供的,所以有既有的XML Schema,如:<configuration>, <runtime>, <assemblyBinding>...等,這些Schema對應到.Net Configuration System的功能。但如果要加入自己的設定Section要如何加呢?在限量教學之前讓我們先來看看一些概念。

Application Configuration File的Schema為樹狀結構,最底層的Schema為<configuration>,接著為<sectionGroup>,最後為<section>。如果要引用自訂的Section,必須在內建Schema <configSections>區間中定義我們要的Schema。你一定會想說那怎麼那些內建Schema都沒有定義?答案是有的,其實系統在運行啟動時,會先去.Net Framework資料夾底下讀取Machine.config,Machine.config裏頭就定義了那些內建Schema,例如:<appSettings>, <runtime>, <system.web>...等,所以App.config可以說是繼承Machine.config。

再來分別說明Section與SectionGroup:

Section

Section為.Net Configuration System區間的最小單位,但是Section裡面還可以有許多巢狀的設定結構,但是內部結構就不叫Section,而是ConfigurationElement。
我們先來看一段自定義Section的區段:
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="Test" type="TestSolution.TestSection, TestSolution" />
  </configSections>
  <Test name="MyTestConfig" type="Console">
    <Comm value="0" />
  </Test>
</configuration>

要自訂Section時,要在<configSections>區間裡定義<configSections>會在系統啟動時先被載入,這樣後面.Net Configuration System才能識別。接著填入name與type的值,name代表的是後面.Net Configuration System要識別的Schema名稱(在上面範例中,限量定義Test的Schema,所以name的值要填"Test");type則是自訂Section的類別路徑,格式就像寫國外郵件地址一樣範圍由小到大(Namespace + Class, Namespace)。我們再來看看自訂Section的類別:

TestSection.cs
public class TestSection : ConfigurationSection
{
    [ConfigurationProperty("name", IsRequired = true)]
    public string Name
    {
        get
        {
            return this["name"] as string;
        }
        set
        {
            this["name"] = value;
        }
    }

    [ConfigurationProperty("type", IsRequired = false)]
    public string Type
    {
        get
        {
            return this["type"] as string;
        }
        set
        {
            this["type"] = value;
        }
    }

    [ConfigurationProperty("Comm")]
    public CommEle Comm
    {
        get
        {
            return this["Comm"] as CommEle;
        }
    }
}

CommEle.cs
public class CommEle : ConfigurationElement
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get
        {
            return this["value"] as string;
        }
        set
        {
            this["value"] = value;
        }
    }
}

概念很簡單,在.Net Configuration類別中,最基本的元素反而是ConfigurationElement,ConfigurationSection就繼承ConfigurationElement。所有在Schema中的Attribute都在類別中用Property表示,並透過ConfigurationProperty指示在Schema中對應的Attribute名稱(例如:在TestSection類別中,Type Property在App.config中對應的是type Attribute)。Section內的巢狀設定都要自訂ConfigurationElement類別(例如:CommEle),而巢狀設定的Property只要設置get就好了,因為
.Net Configuration會自動幫我們產生巢狀設定類別的實體。下面我們來看看如何使用.Net Configuration取得自訂Section:
Program.cs
static void Main(string[] args)
{
    var section = ConfigurationManager.GetSection("Test") as TestSection;
    WriteLine("Name: " + section.Name);
    WriteLine("Type: " + section.Type);
    var comm = section.Comm;
    WriteLine("Value: " + comm.Value);

    /* 執行結果
     * Name: MyTestConfig
     * Type: Console
     * Value: 0
     */
}

使用方式就是要引入System.Configuration,然後使用ConfigurationManager.GetSection方法取得目標Section,因為回傳的是物件,所以記得要轉型成Section類別,接著就可以在VS的Intelligent Sense裡看到我們定義的Property。

使用ConfigurationManager.GetSection是快速的方法,直接針對目標Section去取得。如果想要從整個Config資料去取得Section可以用以下方法:
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var section = config.Sections["Test"] as TestSection;

呼叫ConfigurationManager.OpenExeConfiguration()可以取得整個App.config檔Parse出來的資訊,因為範例中的Section在第一層,所以直接在Section屬性中指定Index Name就可以取得,但若Section是在SectionGroup裡面的話,就要遞迴一層一層的去Parse。


SectionGroup

各Section代表的是不同種類的設定,而SectionGroup可以算是包裝Section的容器。在同一個App.config中,同一個Section是可以出現多次的,但是為了增加識別性就必須在外層套上SectionGroup,以檔案系統比喻來說,Section就像是檔案一樣,每個檔案都是獨立的個體,可是同一個檔案可以到處複製的,所以SectionGroup就像資料夾一樣,裡頭可以有各種不同的Section,而對於不同SectionGroup相同Section的來說,他們的絕對路徑是一定不相同的,藉此才識別,以下為有SectionGroup的情況下,取得Section資訊的範例:

範例情境為一個服務,定義了各個公司所定義的系統參數,這樣一來透過SectionGroup就可以以各公司的設定進而提供服務。

App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="CompanyA">
      <sectionGroup name="Debug">
        <section name="Setting" type="TestSolution.TestSection, TestSolution" />
      </sectionGroup>
      <sectionGroup name="Online">
        <section name="Setting" type="TestSolution.TestSection, TestSolution" />
      </sectionGroup>
    </sectionGroup>
    <sectionGroup name="CompanyB">
      <sectionGroup name="Debug">
        <section name="Setting" type="TestSolution.TestSection, TestSolution" />
      </sectionGroup>
      <sectionGroup name="Online">
        <section name="Setting" type="TestSolution.TestSection, TestSolution" />
      </sectionGroup>
    </sectionGroup>
  </configSections>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <CompanyA>
    <Debug>
      <Setting name="CompanyADebug" type="Console">
        <Comm value="0" />
      </Setting>
    </Debug>
    <Online>
      <Setting name="CompanyAOnline" type="Web">
        <Comm value="1" />
      </Setting>
    </Online>
  </CompanyA>
  <CompanyB>
    <Debug>
      <Setting name="CompanyBDebug" type="Service">
        <Comm value="2" />
      </Setting>
    </Debug>
    <Online>
      <Setting name="CompanyBOnline" type="Window">
        <Comm value="3" />
      </Setting>
    </Online>
  </CompanyB>
</configuration>

Program.cs
static void Main(string[] args)
{
    var companyADebug = ConfigurationManager.GetSection("CompanyA/Debug/Setting") as TestSection;
    var companyAOnline = ConfigurationManager.GetSection("CompanyA/Online/Setting") as TestSection;
    OutputInfo(companyADebug);
    OutputInfo(companyAOnline);

    var companyBDebug = ConfigurationManager.GetSection("CompanyB/Debug/Setting") as TestSection;
    var companyBOnline = ConfigurationManager.GetSection("CompanyB/Online/Setting") as TestSection;
    OutputInfo(companyBDebug);
    OutputInfo(companyBOnline);

    /* 執行結果
     * Name: CompanyADebug
     * Type: Console
     * Value: 0
     * 
     * Name: CompanyAOnline
     * Type: Web
     * Value: 1
     * 
     * Name: CompanyBDebug
     * Type: Service
     * Value: 2
     * 
     * Name: CompanyBOnline
     * Type: Window
     * Value: 3
     */

    Read();
}

static void OutputInfo(TestSection section)
{
    WriteLine("Name: " + section.Name);
    WriteLine("Type: " + section.Type);
    var comm = section.Comm;
    WriteLine("Value: " + comm.Value);
    WriteLine();
}

在這裡使用GetSection,裡面放的Section名稱要是絕對路徑,會比較快找到目標Section。但如果要用整個樹狀結構去找的話可以用以下方法:
static void Main(string[] args)
{
    var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    IterateSectionGroup(config.SectionGroups);

    /* 執行結果
     * Name: CompanyADebug
     * Type: Console
     * Value: 0
     * 
     * Name: CompanyAOnline
     * Type: Web
     * Value: 1
     * 
     * Name: CompanyBDebug
     * Type: Service
     * Value: 2
     * 
     * Name: CompanyBOnline
     * Type: Window
     * Value: 3
     */

    Read();
}

static void IterateSectionGroup(ConfigurationSectionGroupCollection groups)
{
    foreach(ConfigurationSectionGroup group in groups)
    {
 if(group.GetType() == typeof(ConfigurationSectionGroup))
 {
     if(group.SectionGroups.Count > 0)
  IterateSectionGroup(group.SectionGroups);
     foreach(var section in group.Sections)
     {
  OutputInfo(section as TestSection);
     }
 }
    }
}

static void OutputInfo(TestSection section)
{
    WriteLine("Name: " + section.Name);
    WriteLine("Type: " + section.Type);
    var comm = section.Comm;
    WriteLine("Value: " + comm.Value);
    WriteLine();
}

.Net Configuration System的基本使用大概就是這樣,接著後面還有許多種變化的應用,像是巢狀設定, 多設定值陣列, 泛型應用...等,限量會再持續更新。




站內相關文章:

C# - App.config自訂section程式碼架構Part II (巢狀, 陣列)
C# - App.config自訂section程式碼架構Part III (使用泛型)


參考來源:
MSDN - Application Configuration Files





留言