I spent most of yesterday trying to figure out how to make use of a NameValueCollection in a .NET configuration file. After wasting almost the entire night fighting with this problem, I thought I would let everyone know that it is possible, and easier than you might think. (By the way, this was a clear case of working too long on the problem. After a good nights sleep, I solved this in under 5 minutes.)
At this point you might wonder why this was so difficult. The largest and most significant problem is the fact that NameValueCollection isn't serializable (KB 814187). The reason for this is that NameValueCollection doesn't implement ICollection but extends NameObjectCollectionBase instead. The recommended solution is to use a SoapFormatter to serialize and deserialize the collection. While using the SoapFormatter does work, it seemed like a very complex solution for a seemingly simple problem.
Not wanting to over-engineer my solution, I started searching the web for alternatives. I found several newsgroup postings that all said to use the SoapFormatter since that was the recommended solution and the only way to go. Not having drunk too much blue kool-aid, I kept searching. That search turned up an article by Keyvan Nayyeri that shows how to created a serializable NameValueCollection. This approach was intriguing and certainly seemed to be a more useful solution than using the SoapFormatter, but again it seemed like a lot of work.
Keep in mind, all I wanted to be able to do in the configuration file was to create a section that looked like this:
2: <add name="C:\Windows" value="*.dll"/>
3: <add name="C:\Temp"/>
Continuing the search, I thought I found my answer. I stumbled across an article (".NET How To Create and Use Custom Name-Value Config Sections") that shows how to do this in 4 simple steps. Reading through the article, I realized that it is written using the the .NET 1.0 and 1.1 ConfigurationSettings, which is provided for backwards compatibility only. Since the application I'm working on is .NET 3.5 and taking advantage of many of the new language features, I wanted to ensure that I wasn't using any deprecated classes.
This is where I started running into additional problems. It seems that Microsoft provides a NameValueSectionHandler, which the article makes us of. The problem is that NameValueSectionHandler is architected following the .NET 1.0/1.1 model and implements the System.Configuration.IConfigurationSectionHandler interface. This is great if you want to use the deprecated ConfigurationSettings class; if you want to use the recommended System.Configuration.ConfigurationManager or System.Web.Configuration.WebConfigurationManager classes you are out of luck. They will only work with a class that derives from ConfigurationSection.
So, after all this I thought I was out of luck and would need to write my own configuration section handler. (Remember, this was about 1:00 AM.) After sleeping on it, I realized that there was a much simpler way. Along with the NameValueSectionHandler, Microsoft also implemented NameValueConfigurationCollection and NameValueConfigurationElement, which derive from the appropriate configuration classes to be used by the ConfigurationManager. After seeing that, I realized that all I needed to do was implement a NameValueSection which derives from ConfigurationSection.
This is where the solution becomes easy. In it's simplest most form, the NameValueSection looks like this:
1: public class NameValueSection : ConfigurationSection
3: [ConfigurationProperty("", IsDefaultCollection = true)]
4: public NameValueConfigurationCollection Settings
8: return (NameValueConfigurationCollection)base[""];
As you can see, this is pretty simple. In order to use it, you declare the section in your app.config file:
1: <sectionGroup name="customSettings">
2: <section name="copyFiles" type="NameValueSection, CustomConfiguration"/>
5: <add name="C:\Windows" value="*.dll"/>
6: <add name="C:\Temp"/>
To access this configuration section in code, you simply need to do this:
1: NameValueSection nameValueSection = ConfigurationManager.GetSection("copyFiles") as NameValueSection;
2: if (nameValueSection != null)
4: NameValueConfigurationCollection settings = nameValueSection.Settings;
5: foreach (string key in settings.AllKeys)
7: Console.WriteLine(settings[key].Name + ": " + settings[key].Value);
This is about as simple as it gets. Even though you aren't actually using the real NameValueCollection you are using the NameValueConfigurationCollection, which has almost identical behavior. This is the solution that I finally ended up implementing and it works great. As you can see, with a minimal amount of effort you are now able to use a NameValueCollection in your configuration files.