在 .NET Framework 中,無論是 App.Config
或 Web.Config
,均有保留 <configSections>
讓我們可以自訂設定區塊(ConfigurationSection
),由於曾經看過有一些 Library 把設定值放在節點之中,像這樣:
等到要自己弄的時候才發現,似乎沒有那麼簡單,網路上搜尋到的有關於自訂 ConfigurationSection 的文章,大都沒有提到這一塊。
幸好,不是只有我一個人有這樣的問題,Stack Overflow 上的這個問題替我提供了一個方向,假定我的 ConfigurationSection 長這樣,有 Attributes 也有 Elements:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="MySection" type="WindowsFormsApp5.MySection, WindowsFormsApp5" />
</configSections>
...
<MySection>
<MyElement Id="1" Name="Johnny">
<Age>20</Age>
<Address>Taipei</Address>
</MyElement>
</MySection>
</configuration>
如果我們什麼都不做的話,讀取設定的時候會收到一個例外 屬性 'xxx' 不是 ConfigurationElement。
覆寫 DeserializeElement()
要讀取 Age
跟 Address
的值,我們需要去覆寫 DeserializeElement()
方法,自行指定反序列化的方式,下面的程式碼就以 MyElement
為例,一一地將所需的設定值讀取進來。
public class MyElement : ConfigurationElement
{
[ConfigurationProperty("Id")]
public int Id
{
get => (int)this[nameof(this.Id)];
set => this[nameof(this.Id)] = value;
}
[ConfigurationProperty("Name")]
public string Name
{
get => (string)this[nameof(this.Name)];
set => this[nameof(this.Name)] = value;
}
[ConfigurationProperty("Age")]
public int Age
{
get => (int)this[nameof(this.Age)];
set => this[nameof(this.Age)] = value;
}
[ConfigurationProperty("Address")]
public string Address
{
get => (string)this[nameof(this.Address)];
set => this[nameof(this.Address)] = value;
}
protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey)
{
while (reader.MoveToNextAttribute())
{
SetAttributeValue();
}
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
SetElementValue();
}
else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "MyElement")
{
break;
}
}
void SetAttributeValue()
{
switch (reader.Name)
{
case nameof(this.Id):
this[reader.Name] = reader.ReadContentAsInt();
break;
default:
this[reader.Name] = reader.ReadContentAsString();
break;
}
}
void SetElementValue()
{
switch (reader.Name)
{
case nameof(this.Age):
this[reader.Name] = reader.ReadElementContentAsInt();
break;
default:
this[reader.Name] = reader.ReadElementContentAsString();
break;
}
}
}
}
撰寫通用的 ConfigurationElement
如果我們應用程式的設定單純,只有單一種結構的 ConfigurationElement,直接在 DeserializeElement() 方法裡面 Hardcode 也就完了,但是當有第二種、第三種結構出現的時候,我們會需要將覆寫過的 DeserializeElement() 方法給通用化。
由於設定在讀取一次之後就會快取起來,所以我們暫不考慮效能,用 Reflection(反射)來做最快,建立一個 ElementalConfigurationElement
抽象類別,把 DeserializeElement() 改寫一下。
public abstract class ElementalConfigurationElement : ConfigurationElement
{
protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey)
{
var type = this.GetType();
var properties = type.GetProperties().ToDictionary(p => p.Name, p => p);
while (reader.MoveToNextAttribute())
{
if (!properties.TryGetValue(reader.Name, out var property)) continue;
this[reader.Name] = reader.ReadContentAs(property.PropertyType, null);
}
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && properties.TryGetValue(reader.Name, out var property))
{
this[reader.Name] = reader.ReadElementContentAs(property.PropertyType, null);
}
else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == type.Name)
{
break;
}
}
}
}
改這樣之後,只要有從 Element 內容取值的需求,改繼承 ElementalConfigurationElement 就可以了,以上方式提供給有需要朋友做參考。