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

EnhancedSettings - Enhancing application config

Rate me:
Please Sign up or sign in to vote.
4.71/5 (6 votes)
3 Sep 20034 min read 80.6K   930   39   20
Enable multiple environment configurations in a single .config (no longer just appSettings).

Introduction

This is a follow on from my previous article EnhanceAppSettingsHandler, and to get a complete picture I would suggest reading both articles. The previous article discussed how to take the appSettings section of a configuration file and extend it's capabilities to allow us to store multiple sets of configuration in one .config file and decide which on to use at runtime based on information about our environment (hostname etc.).

This article takes it one step further and applies the principals learnt there to create a IConfigurationSectionHandler that can be used to extend any configuration section in the same way just by tweaking to format of the .config file.

Making it generic

The basic principles behind the final EnhancedAppSettings were

  • We replaced the section handler for the appSettings section with our own
  • We defined some new tags that could be contained within the appSettings section to allow us to define configuration sets and map these sets to environments
  • We processed the new format to build up an XmlNode of the old format that could then be processed by the original section handler
  • We maintain full backward compatibility for how the configuration is read so nothing is ever dependent on the new format

If we look at these again we can see that they can easily be applied to any section just as easily with only minor changes to the code

  • We no longer process add / remove / clear nodes explicitly, we just append any nodes that we haven't defined to the new node that we pass to an instance of the original handler
  • We change the tag names of the tags we have defined to include a es_ prefix to reduce the chances of overlap with the configuration we are extending
  • We add an es_baseAttributes tag to allow us to define the attributes to append to the root of our new node (e.g. the appSetting node)
  • We add an attribute to the section tag we are replacing the handler of called es_ParentHandlerType that tells us the type of the original section handler to use to process the new XmlNode we build up. This is specified in the normal way as is it for the normal section tag

This allows us to apply the same principles to any section of configuration that is accessed using ConfigurationSettings.GetConfig (or ConfgiurationSetting.AppSettings) whether part of the original framework or a custom section defined by a third party.

Limitations

This method can only extend section that are accessed using ConfigurationSettings.GetConfig, it cannot extend sections that are read directly from the file - i.e. any sections that use the IgnoreSectionHandler. This means the following sections cannot be extended using this process

  • runtime
  • mscorlib
  • startup
  • system.runtime.remoting

Using the new handler

Making use of the handler requires 3 basic steps:

  1. Make sure the assembly containing the new handler is available to our application
  2. Change the .config file to register our new handle for the section we are extending
  3. Modify the configuration in the section we are extending to use the new features

Registering the new handler

To register the new handler against the section we are extending we need to add some lines at the top of the .config file. The following example replaces the handlers for the appSettings and system.web\customErrors sections.

XML
<configSections>
    <remove name="appSettings"/>
    <section name="appSettings" 
          type="Haley.EnhancedSettings.EnhancedSettingsHandler,
                                    EnhancedSettings"/>
    <sectiongroup name="system.web">
        <remove name="customErrors"/>
        <section name="customErrors" 
          type="Haley.EnhancedSettings.EnhancedSettingsHandler,
                                    EnhancedSettings"/>
    </sectiongroup>    
</configSections>

To make use of these the start tags for the two sections then become

XML
<appSettings es_ParentHandlerType=
        "System.Configuration.NameValueFileSectionHandler, System, 
         Version=1.0.5000.0, Culture=neutral, 
         PublicKeyToken=b77a5c561934e089">
<customErrors es_ParentHandlerType=
        "System.Web.Configuration.CustomErrorsConfigHandler, System.Web, 
        Version=1.0.5000.0, Culture=neutral, 
        PublicKeyToken=b03f5f7f11d50a3a">

and any attributes that would normally be attached directly to these tags are moved to a es_baseAttributes tag. To find the current handler for a particular section first look in the configSections element in the current config file for the application (there may not be one), otherwise refer to the machine.config.

Extending the configuration

This is an example of a new appSettings section, for more details on how this is processed see the previous article EnhancedAppSettingsHandler

XML
<appSettings es_ParentHandlerType=
        "System.Configuration.NameValueFileSectionHandler,
        System, Version=1.0.5000.0, 
        Culture=neutral, PublicKeyToken=b77a5c561934e089">
    <es_baseAttributes 
        thisAttrib="this would break the current specified 
        parent hander as it doesn't support thisAttrib" />
    <!-- this causes us to clear the attributes of the 
                         parent and stop it breaking -->
    <es_baseAttributes /> 
    
    <es_configMap hostname="machine1">
        <es_include set="Dev"/>
    </es_configMap>
    
    <es_configMap hostname="machine2">
        <es_include set="Test"/>
    </es_configMap>
    
    <es_configMap hostname="machine3">
        <es_include set="prod"/>
    </es_configMap>
    
    <!--common to all -->
    <add key="key5" value="value5"/>
    <add key="key6" value="value6"/>
    
    <!-- Dev enivironment -->
    <es_configSet name="Dev">
        <add key="environmentName" value="dev"/>
        <add key="key1" value="dev_value1"/>
        <add key="key2" value="dev_value2"/>
    </es_configSet>
    
    <!-- included in all non-Dev enivironments -->
    <es_configSet name="NonDev">
        <add key="key1" value="nondev_value1"/>
        <add key="key2" value="nondev_value2"/>
    </es_configSet>
    
    <!-- Test enivironment -->
    <es_configSet name="Test">
        <add key="environmentName" value="UAT"/>
        <add key="key3" value="uat_value" />
        <es_include set="NonDev" />
    </es_configSet>
    
    <!-- Production enivironment -->
    <es_configSet name="prod">
        <add key="environmentName" value="Production"/>
        <add key="key3" value="prod_value" />
        <es_include set="NonDev" />
        <remove key="key1" />
    </es_configSet>
</appSettings>

Further extensions

This implementation uses the hostname to decide which configMap nodes to process. It is not always the case that the hostname is the defining factor, it may be:

  • The name of the virtual directory
  • A regular expression match on the hostname
  • The domain the user running the process is logged into
  • Even the actual username running the process (although I wouldn't recommend storing user level config in this way) The only restriction is that if the value of the item you use changes during the execution lifetime of the application the configuration will not be reread.

To change this part of the process there are two steps:

  1. Define a new attribute of the configMap node for the data we wish to match (e.g. virtDir)
  2. Create a new class that inherits from Haley.EnhancedSettings and overrides CheckConfigMap

Here is an example that uses an attribute called hostnameRegEx to allow regular expressions to be specified to match the hostname, it allows either the hostname or hostnameRegEx to be specified and matched, this means we can use hostnameRegEx="dev*" to match any machines with hostnames starting dev

C#
protected override bool CheckConfigMap(XmlNode configMap)
{
    string hostname = null;
    string hostnameRegEx = null;
    XmlAttribute attrib = configMap.Attributes["hostname"];
    if (attrib!=null)
        hostname = attrib.Value;
    attrib = null;
    attrib = configMap.Attributes["hostnameRegEx"];
    if (attrib!=null)
        hostnameRegEx = attrib.Value;
    if ((hostname==null) && (hostnameRegEx==null))
        throw new ConfigurationException("Missing hostname o 
                     configuration node: " + configMap.OuterXml);

    return CheckHostname(hostname) || CheckHostnameRegEx(hostnameRegEx);
}

private bool CheckHostnameRegEx(string hostnameRegEx)
{
    if (hostnameRegEx==null)
        return false;
    else
    {
        Regex re = new Regex(hostnameRegEx, RegexOptions.IgnoreCase);
        return re.IsMatch(System.Environment.MachineName);
    }
}

NB: I have now integrated this into the project

History

  • 30 August 2003 - Version 4.0
  • 08 September 2003 - Version 4.1 - Fixed thread safety issue

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

 
GeneralCode No longer works in .NET 2.0 Framework Pin
Jasmeet Sangari2-Mar-08 16:47
Jasmeet Sangari2-Mar-08 16:47 
GeneralRe: Code No longer works in .NET 2.0 Framework Pin
Paul Haley2-Mar-08 21:03
Paul Haley2-Mar-08 21:03 
GeneralRe: Code No longer works in .NET 2.0 Framework Pin
bobjones6311726-Mar-08 5:20
bobjones6311726-Mar-08 5:20 
GeneralNameValueFileSectionHandler File attribute not supported Pin
Saurabh Gulati1-Aug-06 19:20
Saurabh Gulati1-Aug-06 19:20 
GeneralRe: NameValueFileSectionHandler File attribute not supported Pin
Saurabh Gulati2-Aug-06 1:29
Saurabh Gulati2-Aug-06 1:29 
GeneralRe: NameValueFileSectionHandler File attribute not supported Pin
Paul Haley6-Aug-06 12:42
Paul Haley6-Aug-06 12:42 
GeneralI need costumize an error Pin
Azaliam31-May-06 5:52
Azaliam31-May-06 5:52 
QuestionHow to get it to work Pin
Mike DiRenzo25-Jan-06 7:10
Mike DiRenzo25-Jan-06 7:10 
AnswerRe: How to get it to work Pin
Paul Haley30-Jan-06 10:39
Paul Haley30-Jan-06 10:39 
GeneralRe: How to get it to work Pin
xyzray14-Sep-06 5:06
xyzray14-Sep-06 5:06 
GeneralRe: How to get it to work Pin
xyzray15-Sep-06 4:07
xyzray15-Sep-06 4:07 
GeneralIssue with appSettings and SmartClient Pin
Geir Aamodt4-Apr-05 2:59
Geir Aamodt4-Apr-05 2:59 
GeneralRe: Issue with appSettings and SmartClient Pin
Geir Aamodt4-Apr-05 3:01
Geir Aamodt4-Apr-05 3:01 
GeneralRe: Issue with appSettings and SmartClient Pin
Geir Aamodt4-Apr-05 3:20
Geir Aamodt4-Apr-05 3:20 
GeneralRe: Issue with appSettings and SmartClient Pin
Paul Haley4-Apr-05 11:16
Paul Haley4-Apr-05 11:16 
GeneralRe: Issue with appSettings and SmartClient Pin
Paul Haley4-Apr-05 11:13
Paul Haley4-Apr-05 11:13 
GeneralRe: Issue with appSettings and SmartClient Pin
Geir Aamodt5-Apr-05 23:59
Geir Aamodt5-Apr-05 23:59 
GeneralRe: Issue with appSettings and SmartClient Pin
Paul Haley6-Apr-05 12:19
Paul Haley6-Apr-05 12:19 
GeneralRe: Issue with appSettings and SmartClient Pin
Geir Aamodt6-Apr-05 20:58
Geir Aamodt6-Apr-05 20:58 
GeneralRe: Issue with appSettings and SmartClient Pin
Paul Haley7-Apr-05 12:43
Paul Haley7-Apr-05 12:43 

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.