disclaimer This posting is provided "AS IS" with no warranties, and confers no rights.

 

In my last blog entry, I got a couple of requests for getting “strongly typed” accessors for config. Since the blog format lets me take a meandering walk through configuration, I’ll address things you should do to make your ConfigurationSection-derived class easier to use. First, let me hit some background information because it looks like y’all want some context.

System.Configuration.ConfigurationSettings

In v1.0 and 1.1, there was only one view of configuration: run-time. That view is implemented by an object call System.Configuration.ConfigurationSettings. ConfigurationSettings returns configuration sections through calls to a static method named GetConfig(string sectionName). The sectionName you passed into GetConfig checks to see that the sectionName itself is valid before any config files are opened. The list of valid sectionName strings can be discovered by looking at the configuration section named configSettings. configSettings itself can contain two types of elements:

  • section: Indicates the name of the section and the type that knows how to parse the section. Note: this is not guaranteed to be the same as the type returned by ConfigurationSettings.GetConfig().
  • sectionGroup: Used to “bucket” other sections and sectionGroups. In v1.0 and v1.1 of the framework, this element only had a name attribute. In Whidbey, this element also has a type (more on that in a bit).

Let’s take a look at a hypothetical configuration file:

<configuration>

    <configSettings>

        <section name="sectionOne"

            type="MyNamespace.SectionOne, MyAssembly,..."/>

        <sectionGroup name="sectionGroupOne"

            type="MyNamespace.SectionGroupOne, MyAssembly,...">

            <section name="sectionTwo"

                type="MyNamespace.SectionTwo, MyAssembly,..."/>

            <sectionGroup name="sectionGroupTwo"

                type="MyNamespace.SectionGroupTwo, MyAssembly,...">

                <section name="sectionThree"

                    type="MyNamespace.SectionThree, MyAssembly,..."/>

            </sectionGroup>

        </sectionGroup>

    </configSettings>

</configuration>

Given this set of settings, the following strings can be passed into ConfigurationSettings.GetConfig() and the caller has reason to believe that a value should be returned:

  • sectionOne
  • sectionGroupOne/sectionTwo
  • sectionGroupOne/sectionGroupTwo/sectionThree

What happens here is that the configuration system will take any of these strings and attempt to return a valid object. In v1.0 and v1.1, you will find that most of the objects returned by this call are only understand by the consumers of the configuration. Producers of the configuration (the folks that write the XML) have little hope of being able to make sense of most of the objects that are returned by calls to ConfigurationSettings.GetConfig(). ConfigurationSettings.GetConfig() returns something of type object. Unless you know what type of object to cast to, you have little hope of making sense of the object. In v1.0 and 1.1, that consumer was a runtime thing. The consumer knows what to cast the object to and how to use it.

ConfigurationSettings.GetConfig() looks for xml based on the path passed in via the sectionName argument. In a typical EXE, this search occurs in two files:

  1. machine.config
  2. [appname].exe.config

For a web application, the search happens in two or more files:

  1. machine.config
  2. web.config (in app v-root)
  3. Repeat #2 until you’ve descended roots to the directory the page is hosted in.

Web applications have another interesting wrinkle called location tags. You can find some information about location tags here. A full discussion on this topic is out of scope for this blog entry.

As each of these files is read, the XmlNode at the particular sectionPath location is searched for. When that XmlNode is located, the XmlNode is handed off to the type identified in the section tag for further parsing and validation. Example:

Machine.config has this XML:

<configuration>

    <configSettings>

        <section name="sectionOne"

            type="MyNamespace.SectionOne, MyAssembly,..."/>

    </configSettings>

   

    <sectionOne someTimeSpan="00:30:00"

        someString="Hello, World!" someBool="false"/>

</configuration>

MyApp.Exe.Config has this XML:

<configuration>

    <sectionOne someString="Hello, Application!"/>

</configuration>

The code that consumes the sectionOne section will see the following set of values, assuming that the type picked as a handler merges the sectionOne values in a “typical” fashion:

  • someTimeSpan: 00:30:00
  • someString: Hello, Application!
  • someBool: false

This particular mechanism works just fine for applications consuming the XML. For folks that need to automate the updates to configuration, only one option really existed: load the configuration files into XmlDocument and make your edits against that document. Everyone else edited by hand. Frankly, it’s pretty hard to remember everything that can possibly be set in a particular sectionGroup or section.

Other fun facts about ConfigurationSettings:

  • Values requested through GetConfig() are read from file on the first request and cached for subsequent requests.
  • There is one GetConfig() cache per AppDomain. New AppDomains have to go hit the configuration files anew.
  • Values are cached based on the string passed into GetConfig(). In the first example in this article, the cache would hold, at most, three items.

System.Configuration.Configuration

In Whidbey, a new set of objects was added to System.Configuration. What follows is a list that is exhaustive enough for this discussion:

  • Configuration: Used to load and save configuration files. Machine and Web-based configuration can be edited remotely.
  • ConfigurationSectionGroup: This particular type is only instantiated when called through the Configuration object.
  • ConfigurationSection: This type parses sections and is sometimes the return base type from a call to ConfigurationSettings.GetConfig().
  • ConfigurationElement: Whenever the ConfigurationSection contains sub-elements, this class helps out. ConfigurationElement is also the parent to ConfigurationSection.
  • ConfigurationElementCollection: Used for repeating elements used in collections.
  • ConfigurationProperty: Contains meta-data about the parseable entity. A parseable entity is an XML element or attribute. The metadata includes the following information: element/attribute name, data type, default value, and an indication if the property is required or not. If the property type is a collection, the indicator can also state how to serialize/deserialize the collection.

The Configuration object has the following static methods to load configuration in read/write mode:

  • GetMachineConfiguration() + two overloads: Loads machine.config.
  • GetExeConfiguration(string exePath, ConfigurationUserLevel userLevel): Loads the configuration for a particular EXE. The configuration is either for the EXE or the configuration for the user including the roaming and local settings.
  • GetWebConfiguration(string path) + three overloads: Loads web.config at the specified location.

Each of these three calls returns a Configuration instance. These instances do not share a cache. Each Configuration instance reads configuration independent of the others. These instances stand alone. These instances DO represent the merged view of config (machine.configàapp.config or machine.configàweb.configàweb.config…àweb.config).(Sorry for belaboring the point, but I’ve seen much confusion when I fail to belabor this particular point.) The Configuration instance has two properties that you will be interested in:

    1. SectionGroups: A collection of all the recognized sectionGroups at the scope this particular instance was loaded at.
    2. Sections: A collection of all the recognized sections at the scope this particular instance was loaded at.

SectionGroup has two identical properties. Both of these collections have array indexes with string and integer arguments that return objects. Authors of the sections and sectionGroups are expected to provide a static function that takes a Configuration instance and returns a strongly typed instance of the section or sectionGroup. This is only suggested for sections or sectionGroups that are child elements of the <configuration> element. Example:

public class SectionOne : ConfigurationSection

{

    static public SectionOne(Configuration config)

    {

        return (SectionOne)config.Sections["sectionOne"];

    }

 

    // Getters and setters for properties omitted

}

As the user gets the various sections, changes values, and so on, the underlying code tracks those changes and figures out what it needs to write to the active file. These edits can include the following things:

  • Edits to existing sections.
  • Addition and/or removal of sections and sectionGroups.

The only file that Configuration will ever write to is the file specified by the call to the static Configuration.GetXXX method. Eventually, the user will call the instance method Configuration.Update(). At this point, the changes to configuration are written out. These changes will not be visible to the consumer of configuration until an AppDomain that has never asked for the settings before is created and makes the request.

What’s the difference between these two classes?

ConfigurationSettings is used by the end-consumers of configuration to get configuration from the AppDomain-wide cache. Configuration is used by administrators, setup code, and configuration editors to read and write config for applications and the machine. When you use the Configuration class, you can expect that the type returned by calling Configuration::Sections[] or Configuration::SectionGroups[] is the same as the type indicated in the configuration file.

Exception: The type will not be the same if the type does not inherit from ConfigurationSection and instead implements IConfigurationSectionHandler.

When you call ConfigurationSettings.GetConfig(), you may get back the same type as defined in the configuration file. This is not a guarantee. ConfigurationSection contains a virtual function named GetRuntimeObject(). GetRuntimeObject() allows the author of the ConfigurationSection to store a different representation of the data for consumption by the code that uses the particular set of configuration settings to set behavior or do work.

Summary

This entry is a summary of what exists in configuration. It attempts to delineate the differences between the runtime configuration class, ConfigurationSettings, and the administrative time class, Configuration. There are a lot of interesting things to talk about with respect to configuring applications using the new classes. This is just a foundation for those future entries.

By the way, if you are reading this and are wondering what has happened to any perceived eloquence or good writing that I maybe used to show in my MSDN writings, I have an explanation. I’ve typically worked with good editors at MSDN, Addison Wesley, and Prentice Hall. Here, I’m performing without a net. I’ll gladly fix areas that are poorly worded though J