Click here to Skip to main content
15,881,172 members
Articles / Web Development / ASP.NET

Enhanced AppSettings Configuration Handler

Rate me:
Please Sign up or sign in to vote.
4.68/5 (22 votes)
29 Aug 200311 min read 186.2K   1.7K   62  
Demonstrates how to implement IConfigurationSectionHandler to enhance appSettings for multiple environments.
/*
 * Copyright Paul Haley 2003, phaley:mail.com
 */
using System;
using System.IO;
using System.Collections;
using System.Collections.Specialized;
using System.Xml;
using System.Configuration;

namespace Haley.EnhanceAppSettings
{
	/// <summary>
	/// SectionHandler for an enhanced backward compatible appSettings section
	/// </summary>
	public class EnhancedAppSettingsHandler : IConfigurationSectionHandler
	{
		
		/// <summary>
		/// Override the create method to process the xmlnode as we wish and return a NameValueCollection
		/// </summary>
		/// <remarks>
		/// It has been found (through use of ildasm) that the <c>NameValueFileSectionHandler</c> does not actually return 
		/// a <c>NameValueCollection</c> but a <c>ReadOnlyNameValueCollection</c> which is internal to System.dll 
		/// and inherits from <c>NameValueCollection</c> The problems arise when using 
		/// <c>ConfigurationSettings.AppSettings</c> which casts the returned object to 
		/// <c>ReadOnlyNameValueCollextion</c>, so us returning a <c>NameValueCollection</c> doesn't work.
		/// To get round this we now build up a new <c>XmlNode</c> and pass that to an instance of 
		/// <c>NameValueFileSectionHandler</c> which then returns the correct <c>ReadOnlyNameValueCollection</c> type.
		/// </remarks>
		/// <param name="parent"></param>
		/// <param name="configContext"></param>
		/// <param name="section"></param>
		/// <returns></returns>
		public virtual object Create(object parent,object configContext,XmlNode section)
		{
			// create a new appSettings node to build up the string of settings we actually want
			XmlNode newNode = section.OwnerDocument.CreateNode(XmlNodeType.Element,"appSettings", section.NamespaceURI);
			// parse the config to build up the new XmlNode
			ParseConfigSet(newNode, section);
			// create an instance of the original handler and call it's create method returning the result.
			NameValueFileSectionHandler handler = new NameValueFileSectionHandler();
			return handler.Create(parent, configContext, newNode);
		}

		private Stack processStack = new Stack();

		/// <summary>
		/// Processes a configSet or appSettings xml node which contains configuration information.
		/// Allowed sub nodes: configSet, configMap, add, remove, clear
		/// </summary>
		/// <param name="newNode"></param>
		/// <param name="configSet"></param>
		private void ParseConfigSet(XmlNode newNode, XmlNode configSet)
		{
			/* get the name of the current configSet, see it it is already on the process stack,
			 * if it is then we have an infinte loop in our processing so we will throw an exception
			 * otheriwse push it onto the stack to show which nodes we have processed through
			 */
			XmlAttribute nameAttr = configSet.Attributes["name"];
			string name = null;
			if (nameAttr==null)
				name="appSettings";
			else
				name= nameAttr.Value;

			if (processStack.Contains(name))
			{
				string st = name;
				while(processStack.Count>0)
				{
					st = (string)processStack.Pop() + " --> " + st;
				}
				throw new ConfigurationException("Infintite recursion path found processing configSets: " + st);
			}
			else
			{
				processStack.Push(name);
			}
			// we are clear to process this node so for each of the 
			// contained node call the relevant processing step
			foreach (XmlNode node in configSet.ChildNodes)
			{
				switch(node.Name)
				{
					case "configMap":
						ParseConfigMap(newNode, node);
						break;
					case "add":
					case "remove":
						ParseAddRemove(newNode, node);
						break;
					case "clear":
						ParseClear(newNode, node);
						break;
					case "include":
						ParseInclude(newNode, node);
						break;
					case "configSet":
					case "#comment":
					case "#whitespace":
						break;
					default:
						throw new ConfigurationException("Invalid configuration section found inside appSettings - " + node.Name);

				}
			}
			processStack.Pop();
		}

		/// <summary>
		/// Processes an include node, the set referenced by the node but have 
		/// the same parent as the parent of this include node. However the names 
		/// of configSets must be unique in the entire appSettings block due to the 
		/// way we detect loops if the parsing
		/// </summary>
		/// <param name="newNode"></param>
		/// <param name="node"></param>
		private void ParseInclude(XmlNode newNode, XmlNode node)
		{
			XmlAttribute setName = node.Attributes["set"];
			if (setName!=null)
			{
				// find the configSet specified - must have the same parent as the parent of this include node
				XmlNode configSet = node.SelectSingleNode("../../configSet[@name=\"" + setName.Value + "\"]");
				if (configSet!=null)
				{
					ParseConfigSet(newNode, configSet);
				}	
			}
			else
			{
				throw new ConfigurationException("Missing 'set' attribute in configuration: " + node.OuterXml);
			}
		}

		/// <summary>
		/// Parses a configMap node. 
		/// If the node is found to relate to the current hostname then all of contained
		/// include elements are processes
		/// </summary>
		/// <param name="newNode">the new node we are building</param>
		/// <param name="node">configMap node to process</param>
		private void ParseConfigMap(XmlNode newNode, XmlNode node)
		{
		
			// check if we want to process the configMap
			if (CheckConfigMap(node))
			{
				// we are processing, so for each of the child include nodes we need to find and process the related configSet
				foreach (XmlNode n in node.SelectNodes("include"))
				{
					ParseInclude(newNode, n);
				}
			}
		}

        /// <summary>
        /// Check if we want to process the current machine map
        /// </summary>
        /// <remarks>
        /// This methods is marked as protected to allow other classes to be written that inherit from this one
        /// and use a different mechanism for checking whether or not to process a machine map.
        /// e.g. The path the application was started from
        /// </remarks>
        /// <param name="configMap">the configMap node we are deciding whether or not to process</param>
        /// <returns>true if we should process it otherwise false</returns>
        protected virtual bool CheckConfigMap(XmlNode configMap)
        {
            string hostname = null;
            XmlAttribute attrib = configMap.Attributes["hostname"];
            if (attrib!=null)
                hostname = attrib.Value;
            else
                throw new ConfigurationException("Missing hostname o configuration node: " + configMap.OuterXml);
            return CheckHostname(hostname);
			
        }

		/// <summary>
		/// Appends the instruction nodes to the new node set we will pass down to the original handler type
		/// </summary>
		/// <param name="newNode">the new node we are building up</param>
		/// <param name="node">the add/remove node we are processing</param>
		private void ParseAddRemove(XmlNode newNode, XmlNode node)
		{
			try
			{
				newNode.AppendChild(node.CloneNode(true));
			}
			catch
			{
				throw new ConfigurationException("Invalid configuration: " + node.OuterXml);
			}
		}

		/// <summary>
		/// Remove all the nodes we have added so far we don't need any of them any more
		/// </summary>
		/// <param name="newNode"></param>
		/// <param name="node"></param>
		private void ParseClear(XmlNode newNode, XmlNode node)
		{
			try
			{
				newNode.RemoveAll();
			}
			catch
			{
				throw new ConfigurationException("Invalid configuration: " + node.OuterXml);
			}
		}

		/// <summary>
		/// Check the hostname specified against the current machinename
		/// used to see if we want to process the current configMap node
		/// (This could be extended to use a regEx to provide greater flexibility)
		/// </summary>
		/// <param name="hostname">the hostname from the configMap node</param>
		/// <returns>true - match, false - no match</returns>
		private bool CheckHostname(string hostname)
		{
			return System.Environment.MachineName.ToLower().Equals(hostname.ToLower());
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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 Kingdom United Kingdom
Paul has a background in VB / C++ COM development and has now converted to dotNET, coding in C# for the financial markets industry for the last 3 years.

Comments and Discussions