Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / C#
Article

How to make AppSettings work with multiple values for a key

Rate me:
Please Sign up or sign in to vote.
4.37/5 (22 votes)
6 Jul 20033 min read 266.4K   3K   44   25
Fixing NameValueSectionHandler using reflection and using it seamlessly.

Sample Image - NameValueMultipleSectionHandler.png

Introduction

The NameValueSectionHandler provided with the .NET framework has a serious flaw: despite of NameValueCollection being able to have multiple values for a given key, when using this handler we get only the last value added for that key.

Since ConfigurationSettings.AppSettings, the easiest standard to read app settings uses this handler in a very particular way, developers usually have to create a custom section and read values using ConfigurationSettings.GetConfig and lots of casts.

We'll hack AppSettings to make our life easier, and learn some interesting things.

Why it doesn't work

The reason it doesn't work the way we would expect lies in a single line. Using a de-compiler (like the great Lutz Roeder's Reflector), we'll find the line that adds new values to the collection from the config files looks like this:

C#
collection[key] = value;

instead of

C#
collection.Add(key, value);

As you may know, the first form replaces the value if the key already exists in the collection.

More problems

To make things worse, the returned collection is actually a ReadOnlyNameValueCollection, a derived class. AppSettings' get method casts the NameValueCollection returned by the handler, and since this type is internal, we can't (directly) instantiate it.

But we are all hardcore developers that won't let some silly thing like that bother us, ain't we?

Creating the new handler

Creating a IConfigurationSectionHandler is easy. We get an XmlNode corresponding to the section in the app.config or web.config file, along with the parent section and the context (which is only important in ASP.NET files).

The code for the Create method is pretty straightforward:

C#
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;

Now, the tricky part here is creating a ReadOnlyNameValueCollection. Since it's internal we'll have to create it using reflection.

We'll search for the class and it's constructors in a static method, so it's done only once. In an ASP.NET application, we could use HttpRuntime.Cache to store them and save some extra time (otherwise, this code will get called for each request).

C#
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)});
}

Now, when we need a ReadOnlyNameValueCollection instance, we can use the static CreateCollection method:

C#
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});

}

Using the handler

In order to have AppSettings use our handler instead of the default, we have to override the default by removing the appSettings section and creating a new one. Unfortunately, this has to be done for all our apps, but I think it's worth it.

This is a sample app.config file:

XML
<?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>

Now you can use ConfigurationSettings.AppSettings[key] as always, as well as ConfigurationSettings.AppSettings.GetValues(key), which returns a string[] with all the values for that key.

You'll find an example in the demo project that uses both. Keep in mind that if you read a key that has multiple values with ConfigurationSettings.AppSettings[key] instead of GetValues, you'll get a comma-separated string that has all the values concatenated.

Of course you don't have to override the default behavior if you don't want to. You can create your own configSections that use this handler, and read the values with either the ConfigurationSettings.GetConfig method (you'll have to cast), or with the provided NameValueMultipleSectionHandler.GetConfig which returns a NameValueCollection (it will throw an InvalidCastException it you use it with a section whose handler doesn't return a NameValueCollection).

Suggested reading (from the VS.NET documentation)

History

  • 2003-07-07:

    Optimized the type search

  • 2003-06-11:

    First version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionTake look at this solution Pin
Wahid Bitar6-Apr-15 22:47
professionalWahid Bitar6-Apr-15 22:47 
QuestionGetting error running demo Pin
David Radcliffe30-Oct-13 0:19
David Radcliffe30-Oct-13 0:19 
GeneralThank you! Pin
mr.Michael19804-Nov-09 16:41
mr.Michael19804-Nov-09 16:41 
QuestionDoes it work in .Net 2.0+? Pin
PerlDev16-Sep-07 16:38
PerlDev16-Sep-07 16:38 
AnswerRe: Does it work in .Net 2.0+? Pin
Diego Mijelshon24-Sep-07 7:29
Diego Mijelshon24-Sep-07 7:29 
GeneralRe: Does it work in .Net 2.0+? Pin
jamgray9-Nov-07 7:30
jamgray9-Nov-07 7:30 
GeneralRe: Does it work in .Net 2.0+? Pin
jamgray9-Nov-07 7:48
jamgray9-Nov-07 7:48 
GeneralRe: Does it work in .Net 2.0+? Pin
Juneau18-Jun-08 10:30
Juneau18-Jun-08 10:30 
GeneralNice fix Pin
Michael Sadlon17-May-07 13:29
Michael Sadlon17-May-07 13:29 
GeneralThanks! Pin
Emma Burrows16-Mar-06 22:43
Emma Burrows16-Mar-06 22:43 
GeneralRe: Thanks! Pin
Diego Mijelshon17-Mar-06 1:21
Diego Mijelshon17-Mar-06 1:21 
GeneralUsing it without building a library Pin
Simon Siwik30-Jan-04 2:14
Simon Siwik30-Jan-04 2:14 
GeneralAppSettings writer Pin
pinx11-Dec-03 2:05
pinx11-Dec-03 2:05 
GeneralRe: AppSettings writer Pin
Diego Mijelshon11-Dec-03 2:20
Diego Mijelshon11-Dec-03 2:20 
GeneralI am getting error message Pin
susree11-Sep-03 1:26
susree11-Sep-03 1:26 
GeneralRe: I am getting error message Pin
Diego Mijelshon15-Sep-03 11:19
Diego Mijelshon15-Sep-03 11:19 
GeneralI'm getting an exception Pin
Drew Berkemeyer28-Aug-03 9:04
Drew Berkemeyer28-Aug-03 9:04 
GeneralRe: I'm getting an exception Pin
Drew Berkemeyer28-Aug-03 9:06
Drew Berkemeyer28-Aug-03 9:06 
GeneralRe: I'm getting an exception [modified] Pin
eslamspider6-Aug-06 8:48
eslamspider6-Aug-06 8:48 
GeneralRe: I'm getting an exception Pin
sayarthant28-Nov-07 19:54
sayarthant28-Nov-07 19:54 
GeneralNevermind, I figured it out Pin
Drew Berkemeyer28-Aug-03 9:21
Drew Berkemeyer28-Aug-03 9:21 
GeneralRe: Nevermind, I figured it out Pin
Diego Mijelshon1-Sep-03 10:42
Diego Mijelshon1-Sep-03 10:42 
GeneralNice! Pin
Ian Giffen7-Aug-03 17:53
Ian Giffen7-Aug-03 17:53 
Thank you very much!

This is exactly what I needed to do.
Nice article.

Only one thing I would say (positive criticism that all programmers (myself included) will hear until the end of time).....
your source could use a few comments... although most of what you have in the code is displayed here.

Thanks again and nice job!

Ian Giffen
QuestionWhy? Pin
snkscore19-Jun-03 5:51
snkscore19-Jun-03 5:51 
AnswerRe: Why? Pin
Diego Mijelshon19-Jun-03 9:11
Diego Mijelshon19-Jun-03 9:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.