|
/*
* 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.
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.