Configuration Handlers in .NET
Pages: 1, 2
If the configuration system cannot find a node that matches the path we asked for,
it does not call Create() on the section handler and
ConfigurationSettings.GetConfig() simply returns null.
Returning null is a bit of a pain. Any place we call GetConfig(),
we'll have to check the return value for null and do the right thing, loading defaults if necessary.
That's rather error-prone, but we can wrap this up to make it easier to use.
A factory method on the BasicSettings class that checked for null and
loaded a default, if necessary, would do the trick. We'll move the code that
grabs the settings object from the EntryPoint to our new factory method and rewrite the
EntryPoint to use the new factory method:
using System;
using System.Configuration;
using System.Xml.Serialization;
using cs = System.Configuration.ConfigurationSettings;
namespace BasicConfigSample
{
public class BasicSettings
{
/* same as before */
private BasicSettings() {
FirstName = "<<not";
LastName = "set>>";
}
const string section = "blowery.org/basics";
public static BasicSettings GetSettings() {
BasicSettings b = (BasicSettings)cs.GetConfig(section);
if(b == null)
return new BasicSettings();
else
return b;
}
}
class EntryPoint
{
[STAThread]
static void Main(string[] args) {
BasicSettings settings = BasicSettings.GetSettings();
Console.WriteLine("The configured name is {0}", settings);
}
}
/* SectionHandler stays the same */
}
The astute reader might notice that the default instance looks like a prime
candidate for becoming a Singleton. Luckily, the configuration framework
already caches the result of the call to IConfigurationSectionHandler.Create(),
so it's one less piece we have to implement.
Configuration Parenting
So far, we've covered how to implement a very simple section handler and
how to wrap up the calls to GetConfig() to get around the
null return problem. Next, we're going to dive into configuration
parenting and discuss how it affects our custom section handler.
Remember how an element in the configuration file
that has no matching <section> tag causes the
configuration system to thrown a ConfigurationException? If
you've played around with web.config files, you may be wondering how the
<system.web> section works; no
<configSections> or <section> elements
are present, so how does the configuration system know which
IConfigurationSectionHandler to use? The answer lies in
configuration file parenting.
When the configuration system parses our configuration file, it also
parses a master configuration file, stored in a file called machine.config, which
lives in the Config folder of your framework install directory. Open the file up;
contained within is a long list of <sectionGroup> and
<section> tags at the top of the file.
When the configuration system can't find a section handler in your configuration file, it
walks up to machine.config and checks there. If you decide to register your
section handler in machine.config, you should seriously consider strongly naming
the assembly and registering it with the GAC. That way, anyone who looks in machine.config
can use your configuration handler. Strictly speaking, you don't have to register your assembly
in the GAC, but it's a good idea.
The machine.config file can also hold machine-wide default settings.
If you search machine.config for <system.web>, you'll
find all of the defaults used by ASP.NET. Changes to this file would affect all of the ASP.NET applications
running on that machine.
So what does all this mean to the lowly developer implementing IConfigurationSectionHandler?
Simply, it means that we may have to parse and merge settings from different config files.
In fact, for ASP.NET applications, Create() can be called many times, once for each
directory above the ASP.NET page in question that defines a web.config file, plus possibly once more for
machine.config. For example, if we defined our configuration section handler in
machine.config and had the IIS layout shown below, our configuration handler would be called
four times.
![]() |
| Multiple web.config file diagram |
A couple interesting things about the ASP.NET implementation:
First, if a web.config in the hierarchy doesn't contain settings, it will be
skipped, and the next config file in the hierarchy will be checked. Second, there's a discrepancy between the
ASP.NET configuration system and the DefaultConfigurationSystem used by console and WinForms applications. If a section is redefined in a child configuration file, ASP.NET deals with it and doesn't throw an error. However, a console or WinForms app will throw a ConfigurationException, stating that the section in question has already been defined. I rather like the ASP.NET approach; it supports xcopy deployment (I don't have to know
if the section handler is already registered) and just does what I would expect. At the very least,
it would be nice if the framework teams resolved this difference before v1.1 gets released.
Anyway, back to how parenting affects implementing the interface.
When the configuration system finds a configuration element in machine.config and in
your local config file, it first calls Create() using the XmlNode in
machine.config, then calls Create() using the XmlNode in
our config file. When it calls Create() for our local file, it passes in the
object returned from the call to Create() on machine.config's XmlNode.
We are expected to do the right thing when it comes to merging the current node with the parent settings.
The chaining always starts with machine.config and walks down the directory tree.
Our little section handler from before isn't well-suited for interesting override behavior, so
let's write a new one. This one will sum the value attribute of a <sum>
element. Also, instead of looking for blowery.org/code,
we'll look for blowery.org/sum.
using System;
using System.Configuration;
using System.Xml.Serialization;
using cs = System.Configuration.ConfigurationSettings;
namespace ParentingSample
{
public class Settings
{
const string section = "blowery.org/sum";
private int sum;
internal Settings(int start) {
sum = start;
}
private Settings() {
sum = 0;
}
public int Total {
get { return sum; }
}
internal int Add(int a) {
return sum += a;
}
public override string ToString() {
return Total.ToString();
}
public static Settings GetSettings() {
Settings b = (Settings)cs.GetConfig(section);
if(b == null)
return new Settings();
else
return b;
}
}
class SectionHandler : IConfigurationSectionHandler
{
public object Create(object parent,
object context,
XmlNode section)
{
int num = int.Parse(section.Attributes["value"].Value);
if(parent == null)
return new Settings(num);
Settings b = (Settings)parent;
b.Add(num);
return b;
}
}
}
Notice the new code in the SectionHandler. If parent is not null, we cast it to
a BasicSettings and call Add() with the parsed value. Here, we handle merging
the current node with the parent settings. Otherwise, we start the chain by
creating a new BasicSettings initialized with the first number.
To test this code, we'll need the setting in two config files. In machine.config, we'll register the section handler and base setting like this:
<configuration>
<configSections>
<sectionGroup name="blowery.org>
<section
name="sum"
type=""ParentingSample.SectionHandler, ParentingSample"/>
</sectionGoup>
<!-- other sections -->
</configSections>
<blowery.org>
<sum value="10"/>
</blowery.org>
<!-- other settings -->
</configuration>
In our local application config file, we'll set up another value like this:
<configuration>
<!-- section already registered in machine.config -->
<blowery.org>
<sum value="5"/>
</blowery.org>
</configuration>
Now, if we ran the program, we should see a result of 15. Pretty neat. This example is pretty simple, but it does show you the basics of how to grab the parent settings and merge them with the local settings. The most difficult thing here was deciding how our settings should merge with their parents.
Where Are We?
We've covered quite a bit about implementing a
custom configuration section handler. There are some other techniques that come in handy when working with configuration
files that we have not outlined here. For example, the System.Xml.Serialization namespace in the
System.Xml assembly can radically simplify the parsing code for a configuration section.
Also, take a good look at machine.config for examples of how to structure your configuration to support
parenting and overrides in a flexible, robust manner. ASP.NET does a wonderful job of this and a lot can be learned by
studying how it handles parenting and overrides between machine.config and a web.config file.
Thanks for reading, and I hope you learned a lot from the article. If you'd like to download the accompanying
sample code for this article, you can grab the .zip file from
www.blowery.org/code/ConfigurationSamples.zip.
If you have questions, feel free to contact me via email.
Related Links
- MSDN: Configuring .NET Applications
- MSDN: Configuration Section Settings
- MSDN: IConfigurationSectionHandler
- MSDN: ASP.NET Configuration
Ben Lowery is a developer at FactSet Research Systems, where he works on all things great and small.
Return to ONDotnet.com


