Introduction

All .NET applications are designed to read configuration from either the app.config (Winform or console apps) or web.config (web apps) file. Typically for the developer this means adding configuration to the appSettings section.

This works just fine for most standalone applications while you only need one set of configuration. However there are instances where you need to handle multiple sets of configuration and have the correct set active for the current environment. For example, when you move to enterprise applications, they typically have dependencies on other applications, services and infrastructure; you start to require different configuration for different environments (e.g. development, system testing, user testing, production). This normally means taking one of the following approaches:

maintain different appSetting sections, and comment them in/out as appropriate at deployment time

maintain different .config files and apply the applicable one depending on the environment at deployment time

maintain your own custom configuration format somewhere else

Each of these has associated pitfalls. The first two require an extra step in the deployment process that can either be forgotten with potentially disastrous results, or needs extra work to add it to your deployment scripts. The third option normally works fine for configuration accessed by your own code, but what about configuration used by components you can't or don't want to change? Take for example a dynamic web service reference. When you instantiate the web service proxy object created by VS.NET, it will look in the appSettings to discover the URL for the service, defaulting to wherever it got the original reference from, if the configuration is missing.

While it is possible to hand craft this proxy to change this behavior, any update to the web reference will undo your hard work. Also this is only one example of configuration that is not yours to control where it is read from, there is normally no option to change third party components.

So what we really want is to be able to enhance the appSetting section of the configuration files to allow for different environments, while maintaining compatibility for previously written components to read and if necessary add their own configuration to the section.

Solving the problem

Enter IConfigurationSectionHandler, the interface you need to implement to write your own handlers for sections in the .config files. By implementing our own section handler we obviously have complete control over the structure of the XML within the section and the way in which we process that XML. IConfigurationSectionHandler has only one method we need to implement, publicvirtualobject Create(object parent,object configContext,XmlNode section). Of the arguments Create takes, we are only interested in the last, the XmlNode, this is the entire node from the .config file. It is then up to us to process and return an object, typically a collection containing the configuration. So by implementing our own handler we can customize the format of the appSettings node to solve our problem. At this point it is perhaps important to decide and state the requirements for our new handler.

backward compatible with the normal appSettings from the point of view of any code accessing the configuration

backward compatible with the normal appSettings from the point of view of any code trying to append items in the design environment e.g. dynamic web service references

ability to define named sets of configuration

ability to map configuration sets to a hostname (or other unique feature of the different environments)

specify and defined, predictable order of processing to allow add, remove and clear elements to work as normal

sets of configuration to recursively include other sets to minimize duplication in the configuration

allow components we code to read configuration as normal, allowing them to work in either a normal appSettings environment or our new enhanced one transparently.

These give us 3 constraints:

we must return a ReadOnlyNameValueCollection

we must be able to have add, remove, clear elements directly inside the appSettings element to maintain compatibility with components that add settings at design time - these will be applied to all environments unless we move them

we must use appSettings as our root element

At this stage it is probably useful if you are not already familiar with the standard appSetting handler NameValueFileSectionHandler to read the MSDN documentation for the different available sub-elements of the appSettings element.

Why a ReadOnlyNameValueCollection?

Looking at the documentation for the NameValueFileSectionHandler it appears that it returns a NameValueCollection. However, the first version of this project I coded, returned a NameValueCollection and was found to be incompatible with the ConfigurationSetting.AppSettings property. Initially discovered by another user of this site, dkallen, the property was throwing invalid cast exceptions. To work out why this was, I disassembled System.dll back to IL (using ildasm) to look at the code behind the property. What I found was that, it was returning a ReadOnlyNameValueCollection a type that inherits from NameValueCollection. Looking into this new type, I discovered it was private (internal in c#) to the system.dll assembly. This presented a new problem - how do I return a type that I cannot create? The answer in this case is actually fairly simple - get the original NameValueFileSectionHandler to do it for me. Whereas in the original version of this project we built the NameValueCollection directly. In the new version we build up our own appSettingsXmlNode containing only the child nodes we want (with the exception of clear nodes which we can obviously process ourselves). We then pass this XmlNode to an instance of the original handler getting back a ReadOnlyNameValueCollection that we return directly.

The new format

So now we know what we are trying to achieve, we can define a new format for the appSetting section. The easiest way to show this is with an example and an XSD representation. The example given obviously looks far more complicated than a standard appSettings block but is actually relatively simple and we will go on to describe exactly how it is processed.

Schema

This schema is not used in the code anywhere, it is just an easy way to define the allowed possibilities for the configuration structure. The XSD file is included in the source and can be loaded into tools such as XMLSpy for easy reading.

The XML is recursive with the appSettings block really being an unnamed root: configSet. When a configSet / appSettings node is processed each of the child nodes are processed in the order they appear, with the exception of configSet nodes that are not processed until they are referenced. There are 6 types of nodes that can appear within a configSet, 5 of which can appear directly in the root appSettings node.

configSet

configMap

include (these cannot appear at the appSetting level - they don't make sense there)

add

remove

clear

configSet / appSettings Node

A configSet node is processed by processing each of the child nodes, with the exception of child configSet nodes, in the order they appear as described in each of the sections below.

configMap Node

A configMap node determines which configuration is used. In this implementation it uses the hostname to map one or more configSet nodes. When a configMap is processed we check to see if the specified hostname matches the System.Environment.MachineName. If it does then we process it's children. Processing the child nodes of a configMap involves processing each of the child include nodes in the order they appear. A file may contain more than one configMap for a specific hostname if the order of processing requires it. It is possible to change the behavior of these nodes by overriding the CheckConfigMap function.

include Node

An include node is an instruction to process a specified configSet. The configSet is specified by name using the set attribute of the include node. The configSet to be processed must have the same parent as the parent of the include node.

add Node

An add node works in the same way it does for the default appSettings handler. It adds an item to the collection we are building based on the specified key and value, overwriting any value already contained that has the same key.

remove Node

A remove node works in the same way it does for the default appSettings handler. It removes the item from the collection with the specified key.

clear Node

A clear node works in the same it does for the default appSettings handler. It causes the collection to be emptied.

Example process walk

At this stage it is probably helpful to walk through the example appSettings section above, to see how it is processed. We will walk through an example of how it is processed when running on machine1

Processing each of the children of the appSettings node in order

we process the first configMap that indicates it is linked to our host name

we now process each of the child include nodes causing us to find and process set2

process the add node, appending it to own new node

process the include node causes us to process set1

process the add node to add item key1/value1, appending it to own new node

process the add node to add item key2/value2, appending it to own new node

and then set3

processing the include causes us to process set1 (again)

process the add node to add item key1/value1, appending it to own new node

process the add node to add item key2/value2, appending it to own new node

process the remove node to remove the item key1, appending it to own new node

process the add node to add item key4/value4, appending it to own new node

we ignore the second machine map that is for machine2

process the add node to add item key5/value5, appending it to own new node

process the add node to add item key6/value6, appending it to own new node

What we end up with is, a node containing the add /remove nodes we want. Once this is processed by the NameValueFileSectionHandler we get a collection containing key2/item2, key3/item3, key4/item4, key5/item5, key6/item6

Plugging it in

So now we have our EnhancedAppSettingsHandler, how do we get .NET to use it? This requires a bit of XML at the top of the app.config / web.config

This performs 2 actions, it first removes the default handler that is specified in the machine.config and then adds in our own. The section element has 2 attributes, the name which is the tag name of the elements to process using this handler, and the type which defines the class and assembly for the new handler.

Points of interest

The key to this project is how simple it is to implement your own section handlers to the .config files and how doing so aligns your application's and components better with the .NET configuration architecture and reduces the need to deploy extra configuration files.

Update V2.0

(I have now integrated the changes for V2 into the main article, but have left this here for anyone who wants to know what changed)

Thanks to dkallen for pointing out that use of ConfigurationSettings.AppSettings was failing. I have fixed this in this latest release. This is an important part of maintaining backward compatibility, as failure to do so may break not only your own code but also third party components you use. The problem was that the default handler NameValueFileSectionHandler does not return a NameValueCollection as we are lead to believe by the type returned to us from ConfigurationSettings.AppSettings but rather it returns a ReadOnlyNameValueCollection. This is an internal type to System.dll that inherits from NameValueCollection.

The solution then was that, instead of our code processing the XmlNode and building up a NameValueCollection we instead build up a new XmlNode of appSettings containing only the settings we want to include for this instance of the application, in the standard format. We then create an instance of the original handler, NameValueFileSectionHandler and pass in our new XmlNode, it then passes back the correct ReadOnlyNameValueCollection object that we can then return. This done, all is well and ConfigurationSettings.AppSettings works again.

Update V3.0

I have been asked how to extend this to use a different mechanism for deciding which environment we are currently in and hence which configMap nodes we need to process. So I thought I add a few details here. The first thing I have done is to move the decision out of the ProcessConfigMap function into a dedicated function CheckConfigMap. This new function takes an XmlNode for the configMap as a parameter and returns a bool. I have marked this function as protectedvirtual so that new section handlers can be written that inherit from this type and override the decision making process.

I have also renamed the old machineMap nodes to be configMap nodes, it seemed to make more sense.

If for example we want to change the process so that we look at the AppDomain.BaseDirectory to decide whether or not to process a configMap element, we need to do the following:

add an attribute to the configMap elements called BaseDir

create a new class that inherits from EnhancedAppSettingsHandler

override CheckConfigMap to compare the value of the new attribute to AppDomain.BaseDirectory and return true or false

History

25 August 2003 - Version 3.0 - How to extend to determine current environment from something other than hostname.

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.

Comments and Discussions

Paul! Great Article(s), Thanks. I am learning a lot from these. Have you tried this on .Net 2.0? I have fiddled a bit with your code but am not able to get things going as a result of the ConfigurationExceptionError 'Section or group name 'appSettings' is already defined. Updates to this may only occur at the configuration level where it is defined.'

I have tried to run it in .Net2 ande failed as well, unforunately Microsoft have remove the ability to remove section handlers which means that you can no longer deregister the default appSettings handler and register a new one. I haven't had chance yet to see if I can come up with a work around or whether there is a new way of doing it.

1. The sample App.config has <configsection>. It should be <configSection>.2. For simple configuration scenarios, local.config is a good way to override appSettings. You basically leave it on the file system between deployments with the environment specific settings.

Very clever on the <remove> and <add> to override the frameworks default handler.

First off I would recommend moving to the new version here http://www.codeproject.com/useritems/EnhancedSettings.asp[^]
It provides the same sort of functionality but can be applied to more than just the appSettings block. If you still have problems, then if you post the contents of the config file you're having problems with I'll take a look.

Hi,
Your Enhanced AppSettings Handler is really nice.
Knowing what you know about hacking AppSettings, how feasable is it to implement a Directory.Config, similar to a Web.Config, but for a set of Console applications that reside in a same directory? Could also be seen as an additional layer between MyApp.exe.config and Machine.Config.
In the same lines, how about, MyCommonRoutines.dll.Config ? So you can store appsettings for the common dll

I have had a brief look into doing what you want and it doesn't look too hard. You will need to implement a new IConfigurationSystem. This interface is not really covered in the docs as its not one MS expect you to use however it is public. To see what an implmentation looks like you can use Reflector to take a look at DefaultConfigurationSystem. The Init() method currently loads the machine.config then the app.config, you just need to chain a few more together to suit your needs.
Once you have this new class you then need to call ConfigurationSettings.SetConfigurationSystem, this is an internal method so you will need to use invoke to call it. This call can only be made once and must be made before any configuration is read otherwise you will get an exception. An example of how to do this can be seen by looking at the dissasembly c# for HttpConfigurationSystemBase.EnsureInit().
If I get the time I may write something along these lines and post a new article, looks like an interesting little project and shouldn't take long.

You can use my code any which way your please - it is supplied as is and without and warrenty all I ask is that you leave my name at the top of the source code files. You may also be interested that the version I actually use is an update to this published in the a new article here http://www.codeproject.com/dotnet/EnhancedSettings.asp[^]
Hope you find it helpful.

I have changed a small section for my own use as I wanted to use the include-node from the root-element "appSettings". Originally it was this:
XmlNode configSet = node.SelectSingleNode("../../configSet[@name=\"" + setName.Value + "\"]");

This only allowed "include" to be used from within configMap/configSet. Here's the change:
// FIX: Allow include node to be used from the root element
XmlNode configSet;
if (node.ParentNode.Name == "appSettings")
configSet = node.SelectSingleNode("../configSet[@name=\"" + setName.Value + "\"]");
else
configSet = node.SelectSingleNode("../../configSet[@name=\"" + setName.Value + "\"]");

Can be written a little tidier, but I'll leave that up to you (if you want this behaviour that is).

First thing is have you seen the new version here http://www.codeproject.com/useritems/EnhancedSettings.asp[^]
it is a bit cleaner in implmentation terms and can be used with more than just the app settings blocks. Theorectically it can be used for any block that uses a handler other than the ignore handler.

With respect to your modification I'm not sure I necessarily understand, the modification you have made allows sets to be included under all configurations, the previous implmentation allowed for this simply by putting the normal add, remove, clear nodes directly inside the appSettings node exactly as you would normally. I have to admit this feature is not clear from the article so maybe you just hadn't realised.

You understood me correctly, however I may need to explain why I wanted this behaviour (I know I can add the settings through add, remove & clear directly).

I like to use sets to keep 'sections' of specific settings, making things clearer to overlook. Don't just think about different configurations for specific machines, but groups of settings for a single machine.
For instance, I'm working on servercode, and have a set for the server socket (with settings like ip/port/max connections), as well as a set for the client socket(s) (with settings like connection timeout, buffer size).

This is why I needed the ability to use 'include' directly from within 'appSettings'. I realise I can do this with plain XML comments, but a little more structure/nesting never hurts (think XML visualizers like IE, more pleasing to look at a long listing of short nested blocks than a long list on a single level).

Apart from this usage, I of course also use the configMap for specifying (overriding) different settings for different stage servers/machines.

Was just thinking, an option to 'exclude' a set at any level would be great too I'm thinking.

The exclude option gets a little tricky, particularly with the new version that is independent of the tags used by the normal section handler. All it does is parse the blocks in order building up the xml to pass to the original sectin handler.
Performing an exclude would mean building a new hierarchy of objects with the xml and the reason it was added to allow us to go back and remove it later if we hit an exclude. Then only once we and parsed everything would we then consolidate the xml to pass on. All possible but would need a few hours work, and while I have thought about it before, I've always found I can either reorder the way I'm including things or just use a clear tag and reinclude what I need, so have never persude it any further, but then my config has never been particularly complicated. If you want to give it a go I'd be interested in seeing the results.

I have updated this article here http://www.codeproject.com/useritems/EnhancedSettings.asp[^] to provide a far more genric and fully backward compatible mechanism for extending more than just the appSettings sections in this way.
My view is that any changes to the behaviour of the configuration files should still be 100% backward compatible from the point of view of code accessing it.
As such, while I agree that what you are doing is useful I personally would create a new section for the new handler e.g. appMultiSettings that could be used by objects requiring this functionality without breaking the original compatibilty for those that don't.
The section handler in my new article can be used to extend any section that doesn't use the IgnoreSectionHandler, so could be applied to your section handler either as is or any new handler you created as easily as it could any other.