在配置我们的应用程序的时候,经常会遇到有的配置元素、配置节需要可变的属性。比如,在 .net 2.0 中经常遇到的 Provider 模式。在配置 Provider 的时候,可能会需要为不同的 Provider 提供不同的属性,比如为 SqlDataProvider 提供连接字符串属性,为 XmlFileDataProvider 提供文件路径等。
下面代码是一般的 ConfigurationElement 需要的代码:
1
public class ProviderSettings : ConfigurationElement
2

{
3
private static readonly ConfigurationProperty _propName;
4
private static readonly ConfigurationProperty _propType;
5
private static readonly ConfigurationPropertyCollection _props;
6
7
static ProviderSettings()
8
{
9
_propName = new ConfigurationProperty("name", typeof(string), null, ConfigurationPropertyOptions.IsKey | ConfigurationPropertyOptions.Required);
10
_propType = new ConfigurationProperty("type", typeof(string), null, null, new StringValidator(1, Int32.MaxValue), ConfigurationPropertyOptions.Required);
11
12
_props = new ConfigurationPropertyCollection();
13
_props.Add(_propName);
14
_props.Add(_propType);
15
}
16
17
protected override ConfigurationPropertyCollection Properties
18
{
19
get
{ return _props; }
20
}
21
22
[ConfigurationProperty("name", RequiredValue = true, Options = ConfigurationPropertyOptions.IsKey)]
23
[StringValidator(MinLength = 1)]
24
public string Name
25
{
26
get
{ return (string)base[_propName]; }
27
set
{ base[_propName] = value; }
28
}
29
30
[ConfigurationProperty("type", RequiredValue = true)]
31
[StringValidator(MinLength = 1)]
32
public string Type
33
{
34
get
{ return (string)base[_propType]; }
35
set
{ base[_propType] = value; }
36
}
37
}
为了使 .net 2.0 配置系统允许我们使用自定义的属性(也就是除了已经定义的 name 和 type 属性),我们还需要做一些其他的工作。再看看 ConfigurationElement 的方法和属性,发现有 virtual 的 OnDeserializeUnrecognizedAttribute 和 OnDeserializeUnrecognizedElement 方法:
- OnDeserializeUnrecognizedAttribute: Called when an unknown attribute is encountered while deserializing the ConfigurationElement object
- OnDeserializeUnrecognizedElement: Called when an unknown sub element is encountered while deserializing the ConfigurationElement object
说的再明白不过了,就是允许我们自己处理配置系统不认识的属性(就是除开已经定义的 name 和 type 属性)和子元素(我们这里讨论属性,子元素就先放在一边吧)。既然可以这样,我们就在 OnDeserializeUnrecognizedAttribute 方法里面将不认识的属性放到一个地方不就可以了吗。那再把上面的代码改一改。
1
public ProviderSettings : ConfigurationElement
2

{
3
// Other members
4
.
5
6
private Dictionary<string, string> _params = new Dictionary<string, string>(StringComparer.InvariantCulture);
7
8
/**//// <summary>
9
/// All the undefined attributes.
10
/// </summary>
11
public Dictionary<string, string> Parameters
12
{
13
get
{ return _params; }
14
}
15
16
protected override bool OnDeserializeUnrecognizedAttribute(string name, string value)
17
{
18
Parameters[name] = value;
19
return true;
20
}
21
}
其对应的单元测试代码就不写了,大家自己写吧:)
完成了上面的步骤,其实还是不够的。如果单元测试代码写的好的话,马上就能发现问题了:) 使用上面的方法,在读取配置的时候是没有问题的,可以顺利得从 Parameters 中获取配置的数据。然而在保存的时候就出现问题了。在使用配置的时候,我们在应用程序无论怎么操作 Parameters,保存的时候都不能将改动保存到配置文件中;而且如果 ProviderSettings 中的已定义属性被更改过的话,在保存的时候那些 unrecognized attributes 也消失了。也就是我们只能从配置文件中读取属性而不能修改这些属性!这显然不满足我们的需求!不过从刚才的现象中似乎表示被保存的配置应该都是 Configuration.Properties 中的内容。看来我们还要想办法把那些 unrecognized attributes 放到 Properties 中才行啊。
下面就是改进以后的代码:
1
public ProviderSettings : ConfigurationElement
2

{
3
// Other members
4
//
5
// Note: Since _props will be updated in IsModified-UpdatePropertyCollection,
6
// it cannot be static. And also, the static constructor will be modified.
7
.
8
9
private readonly ConfigurationPropertyCollection _props;
10
private static readonly PredefinedAttributes = new string[]
{ "name","type" };
11
12
public ProviderSettings()
13
{
14
_props = new ConfigurationPropertyCollection();
15
_props.Add(_propName);
16
_props.Add(_propType);
17
}
18
19
protected override bool IsModified()
20
{
21
return (!UpdatePropertyCollection()) ? base.IsModified() : true;
22
}
23
24
private bool UpdatePropertyCollection()
25
{
26
bool isModified = false;
27
28
List props = new List();
29
foreach (ConfigurationProperty prop in Properties)
{
30
if (Array.BinarySearch(PredefinedAttributes, prop.Name) >= 0 ||
31
parameters.ContainsKey(prop.Name))
{
32
continue;
33
}
34
35
props.Add(prop.Name);
36
isModified = true;
37
}
38
39
foreach (string name in props)
{
40
Properties.Remove(name);
41
}
42
43
foreach (KeyValuePair kvp in Parameters)
{
44
string t = GetProperty(kvp.Key);
45
if (t == null || kvp.Value != t)
{
46
SetProperty(kvp.Key, kvp.Value);
47
isModified = true;
48
}
49
}
50
51
return isModified;
52
}
53
54
private bool SetProperty(string name, string value)
55
{
56
ConfigurationProperty prop = null;
57
if (Properties.Contains(name))
{
58
prop = Properties[name];
59
}
60
else
{
61
prop = new ConfigurationProperty(name, typeof(string), null);
62
Properties.Add(prop);
63
}
64
65
if (prop != null)
{
66
base[prop] = value;
67
return true;
68
}
69
else
{
70
return false;
71
}
72
}
73
74
private string GetProperty(string name)
75
{
76
if (Properties.Contains(name))
{
77
ConfigurationProperty prop = Properties[name];
78
if (prop != null)
{
79
return base[prop] as string;
80
}
81
}
82
83
return null;
84
}
85
}