为扩展程序提供可自定义的配置文件的方法(一)

    .Net 2.0推出以后自定义应用程序配置文件已经很简单了,使用默认的自定义配置文件方式需要在编译时就决定需要自定义的节、元素、属性,这对于一般的应用来说已经够了,但是对于需要支持扩展的应用来说,运行时加载的扩展对象可能需要进行一些配置。如果所有的扩展对象配置都一致,例子可以参考System.Diagnostics命名空间的若干TraceListener类,它们的配置文件的属性都是统一的,因此配置文件可以统一。如果扩展对象各有各的配置,由于无法在编译时就确定配置文件应该有哪些元素、属性等,一般的做法就解决不了这个问题。

    请看下面这个配置文件:    

代码
<configuration>
<configSections>
<section name="custom" type="CustomConfiguration.CustomConfigurationSection,CustomConfiguration"/>
</configSections>
<custom name="TestConfiguration">
<add name="example1" type="CustomConfiguration.Example1,CustomConfiguration">
<urls>
<add name="cnblogs" value="http://www.cnblogs.com" />
<add name="microsoft" value="http://www.microsoft.com" />
</urls>
</add>
<add name="example2" type="CustomConfiguration.Example2,CustomConfiguration" dateFormat="yyyy年MM月dd日" />
</custom>
</configuration>

    其中的custom是一个集合,定义了需要加载的扩展类,对于配置文件中定义的Example1和Example2类来说,实现IExample接口,但是各自需要不同的配置来初始化,因此没办法在定义扩展接口时候就决定了他们的配置。解决这个问题有若干种做法,其中System.Configuration框架解决自定义配置文件所采用的办法就是一种办法,WCF里面支持扩展也采用这个办法。另外一个办法就是微软企业库里面所使用的声明式定义。上面这两种办法有点复杂,下面介绍一种简单的办法,思路是通过在加载扩展类的时候就让扩展类自己处理自己的配置文件。

    首先,我们定义一个接口ICustomConfigurationElement,这个接口定义处理配置文件所需要的方法: 

1 public interface ICustomConfigurationElement
2 {
3 void ApplyConfiguration(XmlReader reader);
4 }

 

   然后,我们定义扩展类的需要实现的接口IExample,这个接口实现了上面定义的ICustomConfigurationElement,说明扩展类需要在配置文件中处理自己的配置。

1 public interface IExample : ICustomConfigurationElement
2 {
3 void DoSomething(DateTime now);
4 }

 

    接着,我们定义配置文件中用来配置IExample实现类的配置元素CustomElement<T>,这是一个泛型类,其中Type属性指定了扩展的实现类,Source保存了实例化后的扩展类,下面的代码通过运行时创建扩展类的实现类,由于存在泛型约束,因此T实现了ICustomConfigurationElement接口,表明它能够自己处理配置文件,通过调用ApplyConfiguration方法,实现类就能够获得自己的配置,并正确的进行初始化工作。

代码
public class CustomElement<T> : ConfigurationElement where T : class, ICustomConfigurationElement
{
public<;/span> T Source
{
get;
private set;
}

[ConfigurationProperty(
"name", IsRequired = true, IsKey = true)]
public string Name
{
get
{
return (string)this["name"];
}
set
{
this["name"] = value;
}
}

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

protected override void DeserializeElement(System.Xml.XmlReader reader, bool serializeCollectionKey)
{
Name
= reader.GetAttribute("name");
Type
= reader.GetAttribute("type");
var obj
= Activator.CreateInstance(System.Type.GetType(Type));
T sample
= obj as T;
if (sample == null)
throw new ConfigurationErrorsException(string.Format("提供给type属性的类型[{0}]没有实现接口[{1}].", Type, typeof(T).FullName));
sample.ApplyConfiguration(reader);
Source
= (T)sample;
}
}

 

    再定义一个集合,用来容纳上面定义的元素,以及一个自定义节点。

代码
public sealed class CustomElementCollection<T> : ConfigurationElementCollection where T : class, ICustomConfigurationElement
{
protected override ConfigurationElement CreateNewElement()
{
return new CustomElement<T>();
}

protected override object GetElementKey(ConfigurationElement element)
{
return ((CustomElement<T>)element).Name;
}
}

 

代码
public class CustomConfigurationSection : ConfigurationSection
{
[ConfigurationProperty(
"name", IsRequired = true)]
public string Name
{
get
{
return (string)this["name"];
}
}

[ConfigurationProperty(
"", IsRequired = true, IsDefaultCollection = true)]
public CustomElementCollection<IExample> CustomElements
{
get
{
return (CustomElementCollection<IExample>)this[""];
}
}

private static CustomConfigurationSection _DefaultSection;

public static CustomConfigurationSection Current
{
get
{
if (_DefaultSection == null)
{
lock (typeof(CustomConfigurationSection))
{
if (_DefaultSection == null)
{
_DefaultSection
= (CustomConfigurationSection)ConfigurationManager.GetSection("custom");
}
}
}

return _DefaultSection;
}
}
}

    下面定义了两个实现类Example1、Example2,它们各有不同的配置需求:

代码
public class Example1 : IExample
{
public void DoSomething(System.DateTime now)
{
Console.WriteLine(
"\tCall Test1.DoSomething, {0}", now);
foreach (NameValueConfigurationElement element in Urls)
{
Console.WriteLine(
"\t\tsite name: {0}, site protal: {1}", element.Name, element.Value);
}
}

public NameValueConfigurationCollection Urls
{
get;
set;
}

public void ApplyConfiguration(XmlReader reader)
{
Urls
= new NameValueConfigurationCollection();
bool flag = false;
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "add")
{
Urls.Add(
new NameValueConfigurationElement(reader.GetAttribute("name"), reader.GetAttribute("value")));
}
if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "urls")
{
flag
= true;
}
if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "add" && flag)
break;
}
}

}

 

代码
public sealed class Example2 : IExample
{
public string DateFormat
{
get;
private set;
}

#region IExample 成员

public void DoSomething(System.DateTime now)
{
Console.WriteLine(
"\tCall Test2.DoSomething, {0}", now.ToString(DateFormat));
}

#endregion

#region ICustomConfigurationElement 成员

public void ApplyConfiguration(XmlReader reader)
{
DateFormat
= reader.GetAttribute("dateFormat");
if (reader.IsEmptyElement)
{
reader.Read();
}
else
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "add")
break;
}
}
}

#endregion
}

 

    示例运行代码很简单:

代码
static void Main(string[] args)
{
Console.WriteLine(
"section name: {0}", CustomConfigurationSection.Current.Name);
foreach (CustomElement<IExample> element in CustomConfigurationSection.Current.CustomElements)
{
Console.WriteLine(
"element name: {0}, element type: {1}", element.Name, element.Type);
element.Source.DoSomething(DateTime.Now);
}
}

 

    这个方法实现起来较简单,不需要复杂的其他辅助类,但是这个方法存在以下缺点:无法重用.Net配置文件的基础结构,需要自己手动处理XmlReader;如果一个类处理XmlReader时候发生问题,将会影响到加载下一个扩展类;配置类CustomElement<T>集合了配置职责和工厂职责,不符合单一职责原则。前文提到的另外两个解决办法可以解决要解决上面提到的缺点。

    示例项目文件下载

posted @ 2010-06-10 17:52  wenhx  阅读(420)  评论(0编辑  收藏  举报