5,448,416 members and growing! (18,479 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » .NET Framework » General     Beginner License: The Code Project Open License (CPOL)

Unraveling the Mysteries of .NET 2.0 Configuration

By Jon Rista

Learn how to utilize the powerful new .NET 2.0 configuration features to simplify and centralize your configuration code.
C++/CLI, C#, VB.NET 2.0, Win2K, WinXP, Win2003, Vista, Windows, .NET, .NET 3.0, ASP.NET, Visual Studio, VS2005, Architect, Dev

Posted: 20 Nov 2006
Updated: 28 Jun 2007
Views: 233,757
Bookmarked: 518 times
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
220 votes for this Article.
Popularity: 11.11 Rating: 4.74 out of 5
8 votes, 3.7%
1
1 vote, 0.5%
2
4 votes, 1.8%
3
16 votes, 7.3%
4
190 votes, 86.8%
5

Introduction

One of the wonderful features of .NET has been its XML configuration features. In the days of .NET 1.x, common application settings, database connection strings, ASP.NET web server configuration and basic custom configuration data could be stored in a .config file. Custom configuration sections could use a few basic but custom structures, allowing a small variety of information to be stored in a .config file. More complex configuration, however, was most often accomplished with custom XML structures and custom parsing code. Such code could become quite complex, though, and there are a variety of ways of differing performance to accomplishing the same thing.

With .NET 2.0, the days of writing your own (probably complicated, poorly-performing and tedious) code to manage custom XML configuration structures are over. The custom configuration capabilities of the built-in XML configuration subsystem in .NET 2.0 have been greatly revamped, boasting some extremely useful and time saving features. With relative ease, just about any XML configuration structure you might need is possible with a relatively minimal amount of work. In addition, deserialization of the XML in a .config file can always be overridden. This allows any XML structure necessary to be supported without losing the other advanced features of .NET 2.0 configuration.

The series

This is the first article in a series. All of the core concepts and important bits of knowledge required to begin using .NET 2.0 configuration sections is provided in this first article. However, the configuration framework available with .NET 2.0 is extensive. It has many capabilities and hidden features that can fill in the gaps and oversights in the framework that may seem apparent from this article. To continue learning about the .NET 2.0 configuration framework, additional articles can be found at the following links:

  1. Unraveling the Mysteries of .NET 2.0 Configuration
  2. Decoding the Mysteries of .NET 2.0 Configuration
  3. Cracking the Mysteries of .NET 2.0 Configuration (Pending)

The mystery

I have been using .NET 2.0 for a little over a year now and sadly enough, have spent many a sleepless night tinkering with custom configuration models. Custom XML configuration handling is the ever-persistent performance sink, requiring a new model for each application, depending on what kind of configuration is being stored and how it needs to be accessed (and from where). It wasn't until about eight months ago, while digging through .NET 2.0 Framework assemblies with Reflector, that I came across a curious little class: System.Configuration.ConfigurationSection. Digging a little deeper, I found a multitude of framework classes that derived from ConfigurationSection, as well as several other classes. Ever since then, I have spent more than a few sleepless nights absorbing as much knowledge about the new configuration features of .NET 2.0 as I possibly could.

Suffice it to say, there ultimately is minimal information on the internet about the true capabilities of custom configuration sections. Through internet research, hours of digging through framework code in Reflector, and continual experimentation and use of custom configuration sections, I've finally learned what I needed to know. It's been a life saver. Finally, creating and managing custom XML configuration is simple to write, simple to use, simple to manage and performs wonderfully... and I'm here to share this holy grail with all of you, my fine readers. The only thing I ask in return is to send anyone and everyone you know who uses .config files -- which should be pretty much anyone writing .NET code -- to this page and save them months of digging through code, internet forums and blog posts to learn it all. :)

Topics of configuration

The goal of this article is to cover all the major aspects of .NET 2.0 configuration, as well as expose some of the more closely guarded (read: undocumented... for whatever reasons) secrets that could save you a lot of hassle and time. We'll start with an overview of the core namespace that exposes all of this custom configuration madness. Then we'll move on to specific implementations and uses for custom configuration. The topics of discussion are as follows:
  1. The namespace: System.Configuration
  2. The Object-Model configuration concept
  3. Writing a basic configuration section
  4. Using a custom configuration section
  5. Adding custom elements
  6. Adding element collections
  7. Advanced element collections
  8. Custom configuration section groups
  9. Saving configuration changes
  10. Configuration tips and tricks
  11. Advanced configuration topics
  12. Appendices
    1. Appendix A: The configuration collection cascade
    2. Appendix B: Including external configuration files
  13. Article history

The namespace: System.Configuration

The heart of the new .NET 2.0 configuration gem is the System.Configuration namespace. By default, this namespace is available when the System.dll assembly is referenced. It includes all of the configuration features of .NET 1.1, including the old ConfigurationSettings class (now deprecated in .NET 2.0). To gain access to all of the new .NET 2.0 configuration features, however, you must add a reference to the System.Configuration.dll assembly. It is in this assembly that you will find the core of the new configuration system, the ConfigurationManager static class.

The ConfigurationManager class is a globally accessible doorway to an application's configuration. Since the class is static, all of its members are also static. This makes reading configuration such as AppSettings, ConnectionStrings and custom configuration sections a breeze. While this is similar to the ConfigurationSettings class, it also provides several new features that allow more secure access to an application's configuration. These new features also allow configuration settings to be saved to any configuration section: custom or otherwise.

Beyond the ConfigurationManager class lies the life-blood of custom configuration sections. The following outlines the base classes available to help you write your own configuration object model. THere will also be more on this later. In addition to a set of base classes is a set of validators that can be used to ensure the accuracy of your custom configuration. Also, in the event that your needs are simple enough, a few pre-made configuration sections are available.

The base types

  • ConfigurationSection - The base class of all configuration sections
  • ConfigurationSectionCollection - The base class of a collection of configuration sections
  • ConfigurationSectionGroup - The base class of a configuration section group
  • ConfigurationSectionGroupCollection - The base class of a collection of configuration section groups
  • ConfigurationElement - The base class of a configuration element
  • ConfigurationElementCollection - The base class of a collection of configuration elements
  • ConfigurationConverterBase - The base class of a custom converter *1
  • ConfigurationValidatorBase - The base class of a custom validator *2

The support types

  • ConfigurationManager - Allows global access to all of an application's configuration
  • Configuration - A class that represents an application's configuration
  • ConfigurationProperty - A class that represents a single configuration property
  • ConfigurationPropertyAttribute - An attribute class that supports declarative definition of configuration
  • ConfigurationPropertyCollection - A collection of configuration properties
  • ConfigurationPropertyOptions - Enumeration of possible configuration property options

The validation types

  • CallbackValidator - Allows dynamic validation of a configuration value
  • CallbackValidatorAttribute - Attribute class for declaratively applying callback validators
  • IntegerValidator - Allows validation of an integer (Int32) configuration value
  • IntegerValidatorAttribute - Attribute class for declaratively applying integer validators
  • LongValidator - Allows validation of a long (Int64) configuration value
  • LongValidatorAttribute - Attribute class for declaratively applying long validators
  • PositiveTimeSpanValidator - Allows validation of a positive time span configuration value
  • PositiveTimeSpanValidatorAttribute - Attribute class for declaratively applying positive time span validators
  • RegexStringValidator - Allows validation of a string configuration value with a regular expression
  • RegexStringValidatorAttribute - Attribute class for declaratively applying regex validators
  • StringValidator - Allows validation of a string configuration value
  • StringValidatorAttribute - Attribute class for declaratively applying string validators
  • SubclassTypeValidator - Allows validation that a configuration value derives from a given type *3
  • SubclassTypeValidatorAttribute - Attribute class for declaratively applying subclass type validators
  • TimeSpanValidator - Allows validation of a time span configuration value
  • TimeSpanValidatorAttribute - Attribute class for declaratively applying time span validators

The converter types

  • CommaDelimitedStringCollectionConverter - Converts a comma-delimited value to/from CommaDelimitedStringCollection
  • GenericEnumConverter - Converts between a string and an enumerated type
  • InfiniteIntConverter - Converts between a string and the standard infinite or integer value
  • InfiniteTimeSpanConverter - Converts between a string and the standard infinite TimeSpan value
  • TimeSpanMinutesConverter - Converts to and from a time span expressed in minutes
  • TimeSpanMinutesOrInfiniteConverter - Converts to and from a time span expressed in minutes or infinite
  • TimeSpanSecondsConverter - Converts to and from a time span expressed in seconds
  • TimeSpanSecondsOrInfiniteConverter - Converts to and from a time span expressed in seconds or infinite
  • TypeNameConverter - Converts between a Type object and the string representation of that type
  • WhiteSpaceTrimStringConverter - Converts a string to its canonical format, i.e. white space trimmed from front and back

Pre-made configuration sections

  • AppSettingsSection - This provides the well-known <appSettings> configuration section
  • ConnectionStringsSection - This provides the new <connectionStrings> configuration section
  • ProtectedConfigurationSection - This provides an encrypted configuration section
  • IgnoreSection - Special configuration section wrapper that is ignored by the configuration parser

Premade configuration collections *4

  • CommaDelimitedStringCollection - Used in conjunction with CommaDelimitedStringCollectionConverter
  • KeyValueConfigurationCollection - Used to configure key/value pairs in your configuration sections
  • NameValueConfigurationCollection - Used to configure name/value pairs in your configuration sections

Notes:

  • *1 Custom converters are used to convert between string representations used in XML files to their natively typed representations in a configuration object model.
  • *2 Custom validators are used to validate the accuracy of natively typed data in a configuration object model.
  • *3 This ties into the configuration object model concept, which will be discussed in the next section.
  • *4 These common configuration collections can be used in your custom configuration sections.

In addition to all of the classes described above, you will also find other support types used by ProtectedConfigurationSection. All of the old .NET 1.x configuration classes will also still be available in the System.Configuration namespace, but for the most part they can be ignored.

The Object-Model configuration concept

Before we continue on to creating some custom configuration goodness, it is important to learn about the concept of object-model configuration. The .NET 2.0 configuration system ultimately provides a set of objects that represent your configuration settings structure and provide strongly-typed access to your configuration data. This contrasts with the more common methods of storing and retrieving configuration in XML files, which usually entails reading values through the use of a DOM or streaming reader into variables, and writing changes back through the use of a DOM or streaming writer. In the more advanced configuration systems I personally have written, some caching facilities were included to speed up reading and writing configuration values. Making a configuration file highly customizable while maintaining good performance was always a point of difficulty.

Such monolithic methods of handling XML configuration data are no longer needed with .NET 2.0's configuration object model. As a simple example, take the ConnectionStrings section. By integrating the configuration management for this common configuration item into the ConfigurationManager object, it is very easy to find and access any specific database connection string by a unique name. This is because a .NET collection class exists that lists connection string configuration objects. Each connection string object is keyed on a name value. A mapping of .NET classes to XML elements looks something like this:

ConnectionStringsSection -> <connectionStrings>
ConnectionStringSettingsCollection -> [implicitly created]
ConnectionStringSettings -> <add name="MyConnection" connectionString="blahblah">

Accessing the connection string MyConnection is as simple as the following code:
string myConnectionString = 
    ConfigurationManager.ConnectionStrings["MyConnection"].ConnectionString;

The ConnectionStrings section is extremely simple, so let's take a look at an often-used, but less apparent use of the .NET 2.0 configuration system: the <system.web> configuration group in an ASP.NET web.config file. You may not know it, but this complex configuration section uses the same set of classes that you can use to create your own custom configuration sections. Let's take a look at the class->element relationships for the System.Web.Configuration object model:

SystemWebSectionGroup -> <system.web>
AuthenticationSection -> <authentication>
AuthorizationSection -> <authorization>
CustomErrorsSection -> <customErrors>
CustomErrorsCollection -> [implicitly created]
CustomError -> <error statusCode="404" redirect="...">
HttpModulesSection -> <httpModules>
HttpModuleActionCollection -> [implicitly created]
HttpModuleAction -> <add name="myModule" type="...">
HttpHandlersSection -> <httpHandlers>
HttpHandlerActionCollection -> [implicitly created]
HttpModuleActionCollection -> <add verb="*" type="..." path="...">

This is only a small subset of the full set of configuration sections available for the System.Web ASP.NET configuration section group. All of these settings are accessible through a nicely packaged object model, rooted at the System.Web.Configuration.SystemWebSectionGroup class. In the event that it's required, configuration settings can even be updated and saved back to the web.config file using this object model, assuming that the code making changes and saving has the necessary permissions.

The .NET 2.0 configuration system handles the parsing, validation, security and population of this object model for you. Aside from writing a custom configuration section, which is fairly simple, this completely removes the need to think about XML when you consume your configuration in an application. Not only that, but this nicely packaged, strongly-typed, secure object model wrapped around your configuration is directly accessible anywhere in your application without needing to worry about finding or storing file paths to custom XML configuration files in places like the registry. Gone are the days of hassling with inconsistent, inflexible or poorly performing custom configuration managers; hopefully those days are gone for good.

Writing a basic configuration section

If everything before this has intimidated you, don't worry. Writing the code to provide a custom configuration section to your application is very straightforward and simple. The vast bulk of dealing with XML ugliness, security checks, type conversion, etc., is handled by existing code in the .NET 2.0 Framework. Thanks to the set of base classes available in System.Configuration, the actual volume of code required to create a simple configuration section is very, very low. Let's start by creating a simple configuration section with a string value, a boolean value and a TimeSpan value. Every configuration section must derive from the ConfigurationSection base class, so let's start with the following:

#region Using Statements
using System;
using System.Configuration;
#endregion

namespace Examples.Configuration
{
    /// <summary>
    /// An example configuration section class.
    /// </summary>
    public class ExampleSection: ConfigurationSection
    {
        #region Constructors
        static ExampleSection()
        {
            // Predefine properties here
        }
        #endregion

        // Declare static property fields here

        // Declare expose properties here
    }
}

Once you have the start of a configuration section class, you will need to define the valid configuration properties. A configuration property, represented by the ConfigurationProperty class, describes a single configuration item that will be available in your configuration section. There are a couple ways of defining configuration properties, both programmatic and declarative. My personal preference is to use both methods, as the declarative method helps provide self-describing code, and the programmatic method is more strict. This ensures that only the exact object model you expect is generated and supported, but it is a little more tedious to maintain, as two things must be updated for any change to a single configuration property. Note: In this article, I will always use both methods for the fullest examples.

Let's start filling in the code to make our little example section function. Start by defining static property fields, then create those fields in the static class constructor. Finally, expose the configuration data through C# code properties. The full source code for the custom configuration section should look something like this when it's done:

#region Using Statements
using System;
using System.Configuration;
#endregion

namespace Examples.Configuration
{
    /// <summary>
    /// An example configuration section class.
    /// </summary>
    public class ExampleSection: ConfigurationSection
    {
        #region Constructors
        /// <summary>
        /// Predefines the valid properties and prepares
        /// the property collection.
        /// </summary>
        static ExampleSection()
        {
            // Predefine properties here
            s_propString = new ConfigurationProperty(
                "stringValue",
                typeof(string),
                null,
                ConfigurationPropertyOptions.IsRequired
            );

            s_propBool = new ConfigurationProperty(
                "boolValue",
                typeof(bool),
                false,
                ConfigurationPropertyOptions.None
            );

            s_propTimeSpan = new ConfigurationProperty(
                "timeSpanValue",
                typeof(TimeSpan),
                null,
                ConfigurationPropertyOptions.None
            );

            s_properties = new ConfigurationPropertyCollection();
            
            s_properties.Add(s_propString);
            s_properties.Add(s_propBool);
            s_properties.Add(s_propTimeSpan);
        }
        #endregion

        #region Static Fields
        private static ConfigurationProperty s_propString;
        private static ConfigurationProperty s_propBool;
        private static ConfigurationProperty s_propTimeSpan;

        private static ConfigurationPropertyCollection s_properties;
        #endregion

         
        #region Properties
        /// <summary>
        /// Gets the StringValue setting.
        /// </summary>
        [ConfigurationProperty("stringValue", IsRequired=true)]
        public string StringValue
        {
            get { return (string)base[s_propString]; }
        }

        /// <summary>
        /// Gets the BooleanValue setting.
        /// </summary>
        [ConfigurationProperty("boolValue")]
        public bool BooleanValue
        {
            get { return (bool)base[s_propBool]; }
        }

        /// <summary>
        /// Gets the TimeSpanValue setting.
        /// </summary>
        [ConfigurationProperty("timeSpanValue")]
        public TimeSpan TimeSpanValue
        {
            get { return (TimeSpan)base[s_propTimeSpan]; }
        }

        /// <summary>
        /// Override the Properties collection and return our custom one.
        /// </summary>
        protected override ConfigurationPropertyCollection Properties
        {
            get { return s_properties; }
        }
        #endregion
    }
}

Once you are done, that's it. This custom configuration section is ready to go. If you prefer to write less code, you could drop the static constructor and the static fields and store each value in the default properties collection by a string key. This would lead to less code required for the configuration section itself, but would be less defined in requirements overall. The properties above could be replaced with the following, and the static fields and constructor could be removed. That, by the way, is the purely declarative method:

        #region Properties
        /// <summary>
        /// Gets the StringValue setting.
        /// </summary>
        [ConfigurationProperty("stringValue", IsRequired=true)]
        public string StringValue
        {
            get { return (string)base["stringValue"]; }
        }

        /// <summary>
        /// Gets the BooleanValue setting.
        /// </summary>
        [ConfigurationProperty("boolValue")]
        public bool BooleanValue
        {
            get { return (bool)base["boolValue"]; }
        }

        /// <summary>
        /// Gets the TimeSpanValue setting.
        /// </summary>
        [ConfigurationProperty("timeSpanValue")]
        public TimeSpan TimeSpanValue
        {
            get { return (TimeSpan)base["timeSpanValue"]; }
        }
        #endregion

A quick note about what a ConfigurationProperty really is. By default, unless a custom element is explicitly written, all ConfigurationPropertys defined in a custom configuration element will be represented by XML attributes in an App.config or Web.config file. The creation and collections of custom configuration elements will be discussed later on.

Using a custom configuration section

Now that you have a class written for your custom configuration section, you need to define the custom section in an App.config file. You must and add the necessary XML, so that it can be parsed by the .NET 2.0 configuration system. The App.config changes required to make this ExampleSection available would look like this, assuming that the above code is compiled into Examples.Configuration.dll:

<configuration>
  <configSections>
    <section name="example" type="Examples.Configuration.ExampleSection, 
                                  Examples.Configuration" />
  </configSections>

  <example
    stringValue="A sample string value."
    boolValue="true"
    timeSpanValue="5:00:00"
  />
</configuration>

A quick explanation of the <configSections> element and its children. Unlike built-in configuration sections, which are implicitly defined, custom configuration sections must be explicitly defined. This is done through the <configSections> element and its corresponding <section> child element. It should be noted that the name of a section is generally very important and it can not be chosen arbitrarily. You will see why in a moment. The type of a configuration section maps to a fully qualified class name, followed by the assembly name. Optionally, you may specify culture, version and public key (for signed assemblies) values if you wish to ensure only a specific version of an assembly is searched for when your .config file is parsed. In the example above, the part of the string before the comma is the class name and the part after is the assembly name (excluding the .dll).

Finally, you can consume your custom configuration in code using the ConfigurationManager class. The ConfigurationManager provides a method called GetSection which allows you to access custom configuration settings if any are defined. It's generally best to use a dynamic cast and check for null when accessing custom configuration sections, like so:

private string m_string;
private bool m_bool;
private TimeSpan m_timespan;

void GetExampleSettings()
{
    ExampleSection section = ConfigurationManager.GetSection("example") 
                             as ExampleSection;
    if (section != null)
    {
        m_string = section.StringValue;
        m_bool = section.BooleanValue;
        m_timespan = section.TimeSpanValue;
    }
}

A most important note here is the name chosen to represent the root element of a custom configuration section. In the example App.config file, the section was defined with the name "example." Since the code that loads the section looks for the name 'example' in the call to GetSection(), any attempt to rename the element from "example" to something else in the App.config file will cause the function GetExampleSettings() to fail. It is not possible to arbitrarily choose a name for a custom configuration section as desired unless explicit functionality is written to accommodate such a thing, possibly through the use of another configuration section.

Adding custom elements

By default, all configuration properties defined in a custom configuration section will be represented by attributes in a .config file. This will not always be sufficient, however, and a more complex XML structure consisting of a mix of attributes and elements may be required. Fear not: the .NET 2.0 configuration system fully supports custom configuration elements, each with their own configuration properties that can be attributes or more nested elements. To create a custom configuration element, simply write a class that derives from ConfigurationElement rather than ConfigurationSection. The implementation details for an element will be the same as for a section, with the difference between the two being that elements must be nested within a section.

Let's continue by nesting a custom element inside of our ExampleSection. Let's have this nested element store a DateTime value and an integer value. The code to create this class would be as follows:

#region Using Statements
using System;
using System.Configuration;
#endregion

namespace Examples.Configuration
{
    /// <summary>
    /// An example configuration element class.
    /// </summary>
    public class NestedElement: ConfigurationElement
    {
        #region Constructors
        /// <summary>
        /// Predefines the valid properties and prepares
        /// the property collection.
        /// </summary>
        static NestedElement()
        {
            // Predefine properties here
            s_propDateTime = new ConfigurationProperty(
                "dateTimeValue",
                typeof(DateTime),
                null,
                ConfigurationPropertyOptions.IsRequired
            );

            s_propInteger = new ConfigurationProperty(
                "integerValue",
                typeof(int),
                0,
                ConfigurationPropertyOptions.IsRequired
            );

            s_properties = new ConfigurationPropertyCollection();
            
            s_properties.Add(s_propDateTime);
            s_properties.Add(s_propInteger);
        }
        #endregion

        #region Static Fields
        private static ConfigurationProperty s_propDateTime;
        private static ConfigurationProperty s_propInteger;

        private static ConfigurationPropertyCollection s_properties;
        #endregion

         
        #region Properties
        /// <summary>
        /// Gets the DateTimeValue setting.
        /// </summary>
        [ConfigurationProperty("dateTimeValue", IsRequired=true)]
        public DateTime StringValue
        {
            get { return (DateTime)base[s_propDateTime]; }
        }

        /// <summary>
        /// Gets the IntegerValue setting.
        /// </summary>
        [ConfigurationProperty("integerValue")]
        public int IntegerValue
        {
            get { return (int)base[s_propInteger]; }
        }

        /// <summary>
        /// Override the Properties collection and return our custom one.
        /// </summary>
        protected override ConfigurationPropertyCollection Properties
        {
            get { return s_properties; }
        }
        #endregion
    }
}

Adding this element to the ExampleSection we created earlier is as simple as defining a new configuration property. The following shows what's necessary to expose a nested element:

    public class ExampleSection: ConfigurationSection
    {
        #region Constructors
        /// <summary>
        /// Predefines the valid properties and prepares
        /// the property collection.
        /// </summary>
        static ExampleSection()
        {
            // Create other properties...

            s_propElement = new ConfigurationProperty(
                "nestedElement",
                typeof(NestedElement),
                null,
                ConfigurationPropertyOptions.IsRequired
            );

            s_properties = new ConfigurationPropertyCollection();
            
            // Add other properties...
            s_properties.Add(s_propElement);
        }
        #endregion

        #region Static Fields
        private static ConfigurationProperty s_propElement;
        // Other static fields...
        #endregion

         
        #region Properties
        // ...

        /// <summary>
        /// Gets the NestedElement element.
        /// </summary>
        [ConfigurationProperty("nestedElement")]
        public NestedElement Nested
        {
            get { return (NestedElement)base[s_propElement]; }
        }

        // ...
        #endregion
    }

Finally, using the element in our XML config file is as simple as adding a single <nestedElement> tag inside of the <example> tag. It is important to note that there can only be one instance of nestedElement inside the example element. Creating a nested element in this manner does not allow a collection of like-named elements. It allows a single instance of a particular element, at a specific nesting depth in your custom section. The next section will cover collections of elements in a configuration section. The complete App.config file started previously should look like this:

<configuration>
  <configSections>
    <section name="example" type="Examples.Configuration.ExampleSection, 
                                  Examples.Configuration" />
  </configSections>

  <example
    stringValue="A sample string value."
    boolValue="true"
    timeSpanValue="5:00:00"
  >
    <nestedElement
      dateTimeValue="10/16/2006"
      integerValue="1"
    />
  </example>
</configuration>

Using the new nested element is again extremely simple, as a Nested property will now be exposed on our section variable from the previous usage example:

private string m_string;
private bool m_bool;
private TimeSpan m_timespan;
private DateTime m_datetime;
private int m_int;

void GetExampleSettings()
{
    ExampleSection section = ConfigurationManager.GetSection("example") 
                             as ExampleSection;
    if (section != null)
    {
        m_string = section.StringValue;
        m_bool = section.BooleanValue;
        m_timespan = section.TimeSpanValue;
        m_datetime = section.Nested.DateTimeValue;
        m_int = section.Nested.IntegerValue;
    }
}

Each configuration section can be composed of any number of attributes and elements, nested as deep as required to meet the needs of your application. It's always a good idea to conform to the same XML best practices with custom configuration sections as it is for any other use of XML. As a general rule, data sets or large volumes of information should not be stored in a custom configuration section. We will discuss the reasons for this in the advanced sections. These are "configuration" sections and should be used to store structured application configuration information.

Adding element collections

In the previous section, we created a nested element inside of a configuration section element. The nested element is limited to only one instance and it must appear in the element that defines it in code. Creating collections or lists of elements requires a different and slightly more complex approach. To create a collection or list of configuration elements in a configuration file, you must create a class that derives from ConfigurationElementCollection. One of several varieties of collections can be created, the two primary types being Basic and Add/Remove/Clear.

Anyone who has used the <appSettings> configuration section will be familiar with an Add/Remove/Clear (or ARC map, for short) type of collection. An ARC map is a cascading collection supported in ASP.NET web.config files. A cascading collection allows entries to be added at one web site path level, and removed or cleared at a lower application path level. In addition, any new unique entries added at a lower level will be merged with all of the entries from previous levels. See Appendix A for more details on how a configuration cascade works. A basic map is more restrictive, but allows an element name other than "add" to add items to a collection. An example of a basic map with an alternate element name is System.Web's <customErrors> section, which supports a collection of <error> elements.

Since ARC map collections are the default type, let's create one and add it to our example configuration section from above. The code to create an element collection is slightly more complicated than that of a configuration section or single element, but is still very simple over all. The element collection code below follows a standard pattern used in most element collections in the .NET 2.0 Framework:

[ConfigurationCollection(typeof(ThingElement),
    CollectionType=ConfigurationElementCollectionType.AddRemoveClearMap)]
public class ExampleThingElementCollection: ConfigurationElementCollection
{
    #region Constructors
    static ExampleThingElementCollection()
    {
        m_properties = new ConfigurationPropertyCollection();
    }

    public ExampleThingElementCollection()
    {
    }
    #endregion

    #region Fields
    private static ConfigurationPropertyCollection m_properties;
    #endregion

    #region Properties
    protected override ConfigurationPropertyCollection Properties
    {
        get { return m_properties; }
    }
    
    public override ConfigurationElementCollectionType CollectionType
    {
        get { return ConfigurationElementCollectionType.AddRemoveClearMap; }
    }
    #endregion

    #region Indexers
    public ThingElement this[int index]
    {
        get { return (ThingElement)base.BaseGet(index); }
        set
        {
            if (base.BaseGet(index) != null)
            {
                base.BaseRemoveAt(index);
            }
            base.BaseAdd(index, value);
        }
    }

    public ThingElement this[string name]
    {
        get { return (ThingElement)base.BaseGet(name); }
    }
    #endregion
    
    #region Overrides
    protected override ConfigurationElement CreateNewElement()
    {
        return new ThingElement();
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return (element as ThingElement).Name;
    }
    #endregion
}

It is generally necessary to provide an indexer accessible by numeric index. An indexer accessible by an element's key is also a useful convenience. In the case of this example, the key is a string name. The two overrides, CreateNewElement and GetElementKey, are very important in ensuring that your collection functions properly. There are two overloads of the CreateNewElement function, one that takes no parameters and another that takes an element name, in case you have overridden the default ARC Map behavior. More about this will be discussed in the advanced topics section. By default, the CreateNewElement(string elementName) overload calls CreateNewElement(), so it is not always necessary to override it. The GetElementKey method returns the value of the provided configuration element and returns the value that uniquely identifies it. In the case of our example, the key is the Name property that will be defined on our ThingElement. Finally, you may have noticed that the Properties collection was overridden. The reasons for this are not very apparent unless you delve deep into the .NET Framework source code available here, but suffice it to say that it is a performance optimization.

Our collection is not quite done yet, as we need something to collect. For our example, that something is the ThingElement. This is another ConfigurationElement class, similar to our NestedElement class above.

public class ThingElement: ConfigurationElement
{
        #region Constructors
        /// <summary>
        /// Predefines the valid properties and prepares
        /// the property collection.
        /// </summary>
        static ThingElement()
        {
            // Predefine properties here
            s_propName = new ConfigurationProperty(
                "name",
                typeof(string),
                null,
                ConfigurationPropertyOptions.IsRequired
            );

            s_propType = new ConfigurationProperty(
                "type",
                typeof(string),
                "Normal",
                ConfigurationPropertyOptions.None
            );

            s_propColor = new ConfigurationProperty(
                "color",
                typeof(string),
                "Green",
                ConfigurationPropertyOptions.None
            );

            s_properties = new ConfigurationPropertyCollection();
            
            s_properties.Add(s_propName);
            s_properties.Add(s_propType);
            s_properties.Add(s_propColor);
        }
        #endregion

        #region Static Fields
        private static ConfigurationProperty s_propName;
        private static ConfigurationProperty s_propType;
        private static ConfigurationProperty s_propColor;

        private static ConfigurationPropertyCollection s_properties;
        #endregion

         
        #region Properties
        /// <summary>
        /// Gets the Name setting.
        /// </summary>
        [ConfigurationProperty("name", IsRequired=true)]
        public string Name
        {
            get { return (string)base[s_propName]; }
        }

        /// <summary>
        /// Gets the Type setting.
        /// </summary>
        [ConfigurationProperty("type")]
        public string Type
        {
            get { return (string)base[s_propType]; }
        }

        /// <summary>
        /// Gets the Type setting.
        /// </summary>
        [ConfigurationProperty("color")]
        public string Color
        {
            get { return (string)base[s_propColor]; }
        }

        /// <summary>
        /// Override the Properties collection and return our custom one.
        /// </summary>
        protected override ConfigurationPropertyCollection Properties
        {
            get { return s_properties; }
        }
        #endregion
}

Our ThingElement is very simple, only providing a name, a type and a color. You should notice now that we have overridden the Properties collection on all of our configuration classes so far. This is not a necessary step, but it can improve the performance and efficiency of your configuration sections. More detail about why is outlined in the advanced topics section. We can make this collection accessible in our ExampleSection by adding another ConfigurationProperty, implemented the same way the NestedElement was. Only this time, replace the term NestedElement with ExampleThingElementCollection. Use the element name "things." An example of the configuration file that is now supported by our code looks like this:

<configuration>
  <configSections>
    <section name="example" type="Examples.Configuration.ExampleSection, 
                                     Examples.Configuration" />
  </configSections>

  <example
    stringValue="A sample string value."
    boolValue="true"
    timeSpanValue="5:00:00"
  >
    <nestedElement
      dateTimeValue="10/16/2006"
      integerValue="1"
    />
    <things>
      <add name="slimy" type="goo" />
      <add name="metal" type="metal" color="silver" />
      <add name="block" type="wood" color="tan" />
    </things>
  </example>
</configuration>

Advanced element collections

In the previous section, you learned how to create a standard ARC map type of collection, which is the default. There are a total of four types of collections, two variations of two types: AddRemoveClearMap and AddRemoveClearMapAlternate, and BasicMap and BasicMapAlternate. You know how an AddRemoveClearMap works, from the previous section. A BasicMap is more restrictive than an ARC map in that it does not allow web.config files at a lower level to modify anything inherited from its parent, but it does allow element names other than <add>. The alternate versions of the two main types simply sort elements differently, adding all inherited items so that they are listed last.

In the previous example, we created a collection to list things and each thing was added with an <add> element. For our purposes, full cascaded merging support is probably not necessary and it would be nice to use <thing> as the item element name, rather than <add>. We can use one of a variety of ways to accomplish this, but we will use the most common to modify our original ExampleThingElementCollection class to be a BasicMap and use the element name <thing>:

[ConfigurationCollection(typeof(ThingElement), AddItemName="thing", 
      CollectionType=ConfigurationElementCollectionType.BasicMap)]
public class ExampleThingElementCollection: ConfigurationElementCollection
{
    #region Constructors
    // ...
    #endregion

    #region Fields
    // ...
    #endregion

    #region Properties
    // ...

    public override ConfigurationElementCollectionType CollectionType
    {
        get { return ConfigurationElementCollectionType.BasicMap; }
    }

    protected override string ElementName
    {
        get { return "thing"; }
    }
    #endregion

    #region Indexers
    // ...
    #endregion

    #region Methods
    // ...
    #endregion
    
    #region Overrides
    // ...
    #endregion
}

These simple changes will update our collection class to a BasicMap that expects an element named <thing> in the .config file to add each item, rather than <add>. We can now modify our configuration file like so, which is nicer and clearer than the previous version:

<configuration>
  <configSections>
    <section name="example" type="Examples.Configuration.ExampleSection,
                                     Examples.Configuration" />
  </configSections>

  <example
    stringValue="A sample string value."
    boolValue="true"
    timeSpanValue="5:00:00"
  >
    <nestedElement
      dateTimeValue="10/16/2006"
      integerValue="1"
    />
    <things>
      <thing name="slimy" type="goo" />
      <thing name="metal" type="metal" color="silver" />
      <thing name="block" type="wood" color="tan" />
    </things>
  </example>
</configuration>

Sometimes, when you have a configuration hierarchy and settings cascade from a parent web.config to a child web.config, you need to control which elements appear first in a collection. By default, all elements will be added to a collection in the order in which they are encountered. This order is not particularly well defined when working with multiple web.config files that have been merged. By using an alternate map type, you are given some control over the order of items by forcing all inherited elements to be listed last. A cascade of three web.config files will list the items in the order they are read from each file, starting with the lowest level web.configs first, followed by the parent web.configs and ending with the root web.configs last. Both ARC maps and basic maps support alternates that behave this way, with the added caveat that basic maps prevent child web.config files from modifying items added by their parents.

Custom configuration section groups

Depending on the kind of projects you work on or the specific configuration needs of your application, you may find it useful to group configuration sections. The .NET 2.0 configuration features provide a facility to accomplish this, which we touched on before in our discussion of the <system.web> configuration group for ASP.NET. Creating a configuration section group is simpler than creating a section, but using and accessing them is slightly trickier. Assuming we have two ConfigurationSection classes called ExampleSection and AnotherSection, we could write a ConfigurationSectionGroup like so:

public sealed class ExampleSectionGroup: ConfigurationSectionGroup
{
    #region Constructors
    public ExampleSectionGroup()
    {
    }
    #endregion

    #region Properties
    [ConfigurationProperty("example")]
    public ExampleSection Example
    {
        get { return (ExampleSection)base.Sections["example"]; }
    }

    [ConfigurationProperty("another")]
    public AnotherSection Another
    {
        get { return (AnotherSection)base.Sections["another"]; }
    }
    #endregion
}

Once we have our section group coded, we need to define it in our .config file. This is done very similarly to defining a normal section, only with an added level of hierarchy. It is important to note that the configuration sections ExampleSection and AnotherSection must now be defined as children of our section group:

<configuration>
  <configSections>
    <sectionGroup name="example.group" 
        type="Examples.Configuration.ExampleSectionGroup, 
              Examples.Configuration">
      <section name="example" type="Examples.Configuration.ExampleSection,
                                       Examples.Configuration" />
      <section name="another" type="Examples.Configuration.AnotherSection,
                                       Examples.Configuration" />
    </sectionGroup>
  </configSections>

  <example.group>

    <example
      stringValue="A sample string value."
      boolValue="true"
      timeSpanValue="5:00:00"
    >
      <nestedElement
        dateTimeValue="10\16\2006"
        integerValue="1"
      />
      <things>
        <thing name="slimy" type="goo" />
        <thing name="metal" type="metal" color="silver" />
        <thing name="block" type="wood" color="tan" />
      </things>
    </example>

    <another value="someValue" />

  </example.group>
</configuration>

This configuration section group does just that; it groups two configuration sections together. In addition, it provides a central point of access to those sections. Once we have a reference to our ConfigurationSectionGroup object, we can access the ExampleSection and AnotherSection sections without having to call ConfigurationManager.GetSection() again. However, getting the initial reference to our ExampleSectionGroup isn't quite as simple as getting a single section. Delving a little deeper into the .NET 2.0 configuration classes, we'll find the Configuration class. This class directly represents an application's configuration as defined in its .config file. Like the ConfigurationManager class, Configuration has a GetSection() method and adds the GetSectionGroup() method, as well. You can access a configuration section group, and the sections within it, like so:

private string m_string;
private bool m_bool;
private TimeSpan m_timespan;
private DateTime m_datetime;
private int m_int;

void GetExampleSettings()
{
    Configuration config = 
      ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    ExampleSectionGroup group = config.GetSectionGroup("example.group") 
                                as Exam