WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

Configuration Handlers in .NET

by Ben Lowery
03/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

Next Pagearrow