上次介绍了还有另外两种自定义配置文件的办法,这次介绍WCF所采用的方法。
请看配置文件:


< configuration >
< configSections >
< section name = " extensions " type = " CustomConfiguration.Configuration.ExtensionConfigurationSection, CustomConfiguration " />
< section name = " custom " type = " CustomConfiguration.Configuration.CustomConfigurationSection, CustomConfiguration " />
</ configSections >
< extensions >
< add name = " smtp " type = " CustomConfiguration.Configuration.SmtpSenderElement, CustomConfiguration " />
< add name = " sms " type = " CustomConfiguration.Configuration.SenderElement, CustomConfiguration " />
</ extensions >
< custom >
< senders >
< smtp name = " smtp " semaphore = " 10 " type = " CustomConfiguration.SmtpSender, CustomConfiguration " >
< authorization userName = " abcd " userPassword = " 1234 " />
</ smtp >
< sms name = " sms " semaphore = " 1 " type = " CustomConfiguration.SmsSender, CustomConfiguration " />
</ senders >
</ custom >
</ configuration >
有两个自定义节点,extensions和custom,其中extensions定义了扩展类所使用自定义配置文件的处理类,当程序在custom节点中遇到自定义的节点,就会使用这里定义好的类来处理,这是扩展类自定义配置文件的核心。custom是我们的示例节点,定义了一个senders属性,这是一个集合,里面定义了实现了ISender接口的类,以及实例化这个类所需要的相关配置。
{
bool Send( string message);
void ApplyConfiguration(Configuration.SenderElement element);
}
其中Send方法是业务方法,ApplyConfiguration方法不是必须的,因为实现类可以自行通过访问配置文件来获得自己的配置信息,这里为了简单起见,定义了一个方法,方便使用。
核心类SenderCollection,这是一个配置类,处理custom节点的senders子节点,继承自ConfigurationElement。虽然是一个Collection,但是并没有继承自ConfigurationElementCollection,原因是ConfigurationElementCollection只能创建同一类型的元素集合,要想创建不同类型的集合,只能自己实现,这也就是本文的重点。代码如下:


{
private readonly List < SenderElement > m_Elements = new List < SenderElement > ();
protected override void DeserializeElement(System.Xml.XmlReader reader, bool serializeCollectionKey)
{
if (reader.HasAttributes && ( 0 < reader.AttributeCount))
{
while (reader.MoveToNextAttribute())
{
if ( this .Properties.Contains(reader.Name))
{
base [reader.Name] = this .Properties[reader.Name].Converter.ConvertFromString(reader.Value);
}
else
{
this .OnDeserializeUnrecognizedAttribute(reader.Name, reader.Value);
}
}
}
if (XmlNodeType.Element != reader.NodeType)
{
reader.MoveToElement();
}
XmlReader reader2 = reader.ReadSubtree();
if (reader2.Read())
{
while (reader2.Read())
{
if (XmlNodeType.Element == reader2.NodeType)
{
SenderElement element = this .CreateNewSection(reader2.Name);
m_Elements.Add(element);
element.DeserializeInternal(reader2, false );
}
}
}
}
private SenderElement CreateNewSection( string name)
{
var typeName = ExtensionConfigurationSection.Current.Extensions.GetElement(name);
var local = (SenderElement)Activator.CreateInstance(Type.GetType(typeName));
local.InternalInitializeDefault();
return local;
}
#region IEnumerable<SenderElement> 成员
public IEnumerator < SenderElement > GetEnumerator()
{
return m_Elements.GetEnumerator();
}
#endregion
#region IEnumerable 成员
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
重载了DeserializeElement方法,自己来实现了xml文件的解析,中间的关键步骤在于每读到一个子节点,就调用CreateNewSection方法来创建一个自定义的配置元素对象,创建的方法是通过查找extensions节点来获得自定义配置元素的类型,然后创建这个类型的实例化对象,并且调用初始化方法。创建完毕以后调用DeserializeInternal方法来让配置文件自己处理自己的节点,这是重用System.Configuration框架的关键,通过这个可以采用声明式的方法来定义自己的配置类,而无需像上篇文章一样手动处理XmlRead。由于CreateNewSection需要通过查找来获得对应配置类,这也是为什么要把extensions独立出来一个配置节点,否则由于当前配置节点还没有处理完毕,这个时候访问当前配置节点,会造成循环调用,结果就是堆栈溢出。
Main方法如下,循环遍历senders,构造对象,调用send方法测试:


{
foreach (SenderElement e in Configuration.CustomConfigurationSection.Current.Senders)
{
ISender sender = (ISender)Activator.CreateInstance(Type.GetType(e.Type));
sender.ApplyConfiguration(e);
sender.Send( " Hello world! " );
}
Console.ReadLine();
}
示例项目文件下载