如何使 AppSettings 能够为一个键使用多个值






4.37/5 (20投票s)
2003 年 6 月 11 日
3分钟阅读

283861

2992
使用反射修复 NameValueSectionHandler 并无缝使用它。
引言
.NET 框架提供的 NameValueSectionHandler
存在一个严重缺陷:尽管 NameValueCollection
能够为一个给定的键提供多个值,但当使用此处理程序时,我们只能获得该键添加的最后一个值。
由于 ConfigurationSettings.AppSettings
(读取应用程序设置的最简单标准)以一种非常特殊的方式使用此处理程序,开发人员通常必须创建一个自定义部分,并使用 ConfigurationSettings.GetConfig
和大量的强制转换来读取值。
我们将破解 AppSettings
以使我们的生活更轻松,并学习一些有趣的事情。
为什么它不起作用
它不起作用的原因在于一行代码。使用反编译器(例如 Lutz Roeder 出色的 Reflector),我们将找到将新值添加到配置文件的集合中的那行代码,如下所示
collection[key] = value;
而不是
collection.Add(key, value);
您可能知道,如果该键已存在于集合中,则第一种形式会替换该值。
更多问题
更糟糕的是,返回的集合实际上是一个 ReadOnlyNameValueCollection
,一个派生类。AppSettings 的 get
方法转换了处理程序返回的 NameValueCollection
,并且由于此类型是内部的,我们(不能直接)实例化它。
但我们都是硬核开发人员,不会让这种愚蠢的事情困扰我们,对吧?
创建新处理程序
创建 IConfigurationSectionHandler
很容易。我们得到一个对应于app.config 或 web.config 文件中该部分的 XmlNode
,以及父部分和上下文(这仅在 ASP.NET 文件中很重要)。
Create
方法的代码非常简单
NameValueCollection collection = null;
//Check if we have found the ReadOnlyNameValueCollection Type
if (readOnlyNameValueCollectionType != null)
{
collection = CreateCollection(parent);
foreach (XmlNode xmlNode in section.ChildNodes)
{
//Skip non-elements like section attributes, comments, etc
if (xmlNode.NodeType == XmlNodeType.Element)
{
switch (xmlNode.Name)
{
case "add":
collection.Add(xmlNode.Attributes["key"].Value,
xmlNode.Attributes["value"].Value);
break;
case "remove":
collection.Remove(xmlNode.Attributes["key"].Value);
break;
case "clear":
collection.Clear();
break;
}
}
}
}
return collection;
现在,这里的棘手部分是创建一个 ReadOnlyNameValueCollection
。由于它是内部的,我们必须使用反射来创建它。
我们将在 static
方法中搜索类及其构造函数,因此它只执行一次。在 ASP.NET 应用程序中,我们可以使用 HttpRuntime.Cache
来存储它们并节省一些额外的时间(否则,此代码将为每个请求调用)。
readOnlyNameValueCollectionType = typeof(NameValueCollection).
Assembly.GetType("System.Configuration.ReadOnlyNameValueCollection");
if (readOnlyNameValueCollectionType != null)
{
//Get the constructors for the specified parameter types
readOnlyNameValueCollectionConstructor1 =
readOnlyNameValueCollectionType.GetConstructor(
new Type[] {readOnlyNameValueCollectionType});
readOnlyNameValueCollectionConstructor2 =
readOnlyNameValueCollectionType.GetConstructor(
new Type[] {typeof(IHashCodeProvider), typeof(IComparer)});
}
现在,当我们需要一个 ReadOnlyNameValueCollection
实例时,我们可以使用 static
CreateCollection
方法
if (parent == null)
{
//Create empty collection
return
(NameValueCollection)readOnlyNameValueCollectionConstructor2.Invoke(
new object[] {
new CaseInsensitiveHashCodeProvider(
CultureInfo.InvariantCulture),
new CaseInsensitiveComparer(
CultureInfo.InvariantCulture)});
}
else
{
//Copy parent elements
return
(NameValueCollection)readOnlyNameValueCollectionConstructor1.Invoke(
new object[] {parent});
}
使用处理程序
为了让 AppSettings
使用我们的处理程序而不是默认处理程序,我们必须通过删除 appSettings
部分并创建一个新的部分来覆盖默认处理程序。不幸的是,这必须针对我们所有的应用程序完成,但我认为这是值得的。
这是一个示例 app.config 文件
<?xml version="1.0" encoding="Windows-1252" ?>
<configuration>
<configSections>
<remove name="appSettings" />
<section name="appSettings"
type="DigBang.Configuration.NameValueMultipleSectionHandler,
Configuration" />
</configSections>
<appSettings>
<add key="file" value="myfile1" />
<add key="file" value="myfile2" />
<add key="connectionString" value="my connection string" />
<add key="another multiple values key" value="my value 1" />
<add key="another multiple values key" value="my value 2" />
<add key="another multiple values key" value="my value 3" />
</appSettings>
</configuration>
现在,您可以像往常一样使用 ConfigurationSettings.AppSettings[key]
,以及 ConfigurationSettings.AppSettings.GetValues(key)
,它返回一个 string[]
,其中包含该键的所有值。
您将在演示项目中找到一个示例,该示例使用了这两种方法。请记住,如果您使用 ConfigurationSettings.AppSettings[key]
而不是 GetValues
读取具有多个值的键,您将得到一个以逗号分隔的字符串,其中包含所有连接的值。
当然,如果您不想,则不必覆盖默认行为。您可以创建自己的 configSections
,使用此处理程序,并使用 ConfigurationSettings.GetConfig
方法(您必须进行强制转换)或提供的 NameValueMultipleSectionHandler.GetConfig
方法(它返回一个 NameValueCollection
)(如果将其与其处理程序未返回 NameValueCollection
的部分一起使用,它将抛出 InvalidCastException
)。
建议阅读(来自 VS.NET 文档)
历史
- 2003-07-07:
优化类型搜索
- 2003-06-11:
第一版