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:
- Make sure the assembly containing the new handler is available to our application
- Change the .config file to register our new handle for the section we are extending
- 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.
<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
<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
<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" />
-->
<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>
-->
<add key="key5" value="value5"/>
<add key="key6" value="value6"/>
-->
<es_configSet name="Dev">
<add key="environmentName" value="dev"/>
<add key="key1" value="dev_value1"/>
<add key="key2" value="dev_value2"/>
</es_configSet>
-->
<es_configSet name="NonDev">
<add key="key1" value="nondev_value1"/>
<add key="key2" value="nondev_value2"/>
</es_configSet>
-->
<es_configSet name="Test">
<add key="environmentName" value="UAT"/>
<add key="key3" value="uat_value" />
<es_include set="NonDev" />
</es_configSet>
-->
<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:
- Define a new attribute of the
configMap
node for the data we wish to match (e.g. virtDir
)
- 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
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