Configuration Handlers in .NET
by Ben Lowery03/17/2003
Extending the CLR Configuration System
The registry is dead! Long live the registry! In the shiny new world of .NET-based applications with xcopy deployment and smart clients,
we can no longer use the registry to hold application configuration information.
Instead, we're supposed to use XML-based configuration files accessed via the
CLR's pluggable configuration framework, System.Configuration, which lives
in the System assembly. This article will introduce you to the configuration
system and show you how to extend it using custom configuration section handlers.
Overview of the Configuration System
To start, let's take a look at how the configuration system works and for what it was
intended. The configuration system works by reading in settings from specially-named XML files.
For console and WinForms applications, the file has the same name as the executable, plus a
.config extension. For example, if you had an application named MyApp.exe,
the config file would be named MyApp.exe.config. For ASP.NET applications,
the config file is always named web.config. Either way, configuration files
are meant to be read-only, and you'll find nothing in the configuration framework that
helps you write to configuration files.
Configuration files are meant for application-level
settings that rarely change after installation, not user-level settings like window
placement or favorite color. Per-user settings
should be stored elsewhere, preferably in an application-specific folder under
the user's Application Data folder, inside of Isolated Storage, or even inside of the
dreaded registry (under HKEY_CURRENT_USER/Software/YourCompany/). That said,
if we did need to modify one, configuration files are just XML files.
We can use the standard facilities present in the System.Xml assembly
to do whatever it is we need to do. Be warned, this may not always work out. For applications
deployed using HTTP (like Chris Sells' Wahoo app), we would not have any way to write out the
configuration file. Also, if we do write settings back to our config file from inside of an
ASP.NET application, the ASP.NET application will be restarted.
Configuring Configuration
Moving right along, let's take a look at how a configuration file is structured.
Configuration files are broken up into two main parts. The first part, contained within a
<configSections> element, is really metadata that the framework
uses to determine how to parse the remainder of the file.
Let's take a look at a sample layout:
<configuration>
<configSections>
<sectionGroup name="blowery.org">
<section name="basics"
type="BasicConfigSample.SectionHandler, BasicConfigSample"/>
</sectionGroup>
</configSections>
<blowery.org>
<basics>
<firstName>Jack</firstName>
<lastName>Hoya</lastName>
</basics>
</blowery.org>
</configuration>
Here we have one <sectionGroup> which contains one
<section>. The <section> has a
name and a type. The name specifies
the name of the XML element containing the configuration section;
the type specifies the class that will be
used to parse the configuration section. The <sectionGroup>
can group different sections together under a common parent element.
In the sample above, we're stating that we have a base element,
<blowery.org>, and within that base we have a section, <basics>.
When the framework parses the <basics> section, it should use the
BasicConfigSample.SectionHandler class from the BasicConfigSample
assembly.
To kick off the parser and access the settings for a configuration section, we call
ConfigurationSettings.GetConfig("sectionName"). For the example above,
the call would be ConfigurationSettings.GetConfig("blowery.org/basics").
Notice that we have to specify the path down to the section when we ask for the settings.
Given this little snippet of configuration file, let's go over what's happening when
we call ConfigurationSettings.GetConfig("blowery.org/basics").
First, the configuration system parses the <configSections>
looking for <section> tags. For each <section> found,
the configuration system creates an instance of the type specified in the
type attribute and associates the configuration path with the instance.
The type must implement the IConfigurationSectionHandler interface.
In this case, the framework instantiates a BasicConfigSample.SectionHandler,
casts it to a IConfigurationSectionHandler, and
associates the instance with the path blowery.org/basics. When we call
GetConfig(), Create() is called and the framework passes
on the return value to our calling program.
If any nodes are found that don't match an IConfigurationSectionHandler
instance, a ConfigurationException is thrown, and processing halts.
We could catch this exception, but we generally wouldn't want to. The error may or may not
be due to a misconfiguration of your portion of the configuration file, so catching it and
eating it wouldn't really be safe. If we do catch it, we should at least rethrow it.
Implementing the Interface
Now we need to create a simple object that implements IConfigurationSectionHandler
and use it from our code. First, here's the definition of the interface:
public interface IConfigurationSectionHandler {
object Create(object parent,
object configContext,
XmlNode section);
}
To implement the interface, all we need is a Create() method that takes
three parameters. That seems pretty easy. Before we implement it, let's go over the
parameters and the return value.
The first parameter, parent, is typed as System.Object.
The parent is used when we need to support chains of configuration files,
which we're not going to do until later. For now, we'll ignore this one. The second
parameter, configContext, is currently only used when the
IConfigurationSectionHandler is being used by an ASP.NET application. When
the handler is called by ASP.NET, this instance will be an HttpConfigurationContext.
Again, we're going to ignore this for the time being. The last parameter, section,
is an XmlNode that represents the configuration section. In our case,
the XmlNode will point to the <basics> element. Finally, when
we're done, we need to return an object that represents our configuration settings. We should
define a type for our settings (we'll use BasicSettings) and document that our
implementation returns that type. Here's our implementation:
using System;
using System.Configuration;
using System.Xml;
namespace BasicConfigSample
{
public class SectionHandler : IConfigurationSectionHandler
{
/// <summary>Returns a BasicSettings instance</summary>
public object Create(object parent,
object context,
XmlNode section) {
string f = section["firstName"].InnerText;
string l = section["lastName"].InnerText;
return new BasicSettings(f,l);
}
}
public class BasicSettings
{
internal BasicSettings(string first, string last) {
FirstName = first;
LastName = last;
}
public readonly string FirstName;
public readonly string LastName;
public override string ToString() {
return FirstName + " " + LastName;
}
}
}
As stated before, to use the section handler, we need to ask the
ConfigurationSettings object for the proper config section:
using System;
using System.Configuration;
namespace BasicConfigSample
{
class EntryPoint
{
const string mySection = "blowery.org/basics";
[STAThread]
static void Main(string[] args) {
BasicSettings settings;
settings = (BasicSettings)ConfigurationSettings.GetConfig(mySection);
Console.WriteLine("The configured name is {0}", settings);
}
}
}
And there we have it, our very own custom section handler, an equal citizen with all of the other section handlers supplied by the framework.
Missing Settings and Defaults
So far, we've only talked about the case where everything exists and is configured properly. What happens if the configuration settings are not present in the config file? To illustrate, what happens if we have a config file that looks like this?
<configuration>
<configSections>
<sectionGroup name="blowery.org">
<section name="basics"
type="BasicConfigSample.SectionHandler, BasicConfigSample"/>
</sectionGroup>
</configSections>
<!-- missing section!! -->
</configuration>
Pages: 1, 2 |

