©2007 Alex Homer, Stonebroom Limited, http://www.stonebroom.com
This series of three articles shows how you can create custom configuration sections in your ASP.NET application configuration file, and expose the data they contain within your application using custom providers. The articles also describe how you can use Windows Group Policy at both the machine and domain level to control configuration settings from a central location, and impose these settings on some or all of the servers that run your Web applications. Finally, the third article shows an example of how these techniques are used to great effect in existing products; by demonstrating how you can use the Manageable Configuration Provider that is part of Microsoft's Enterprise Library.
Defining and Handling Custom Configuration Sections
An Example of a Custom Configuration Section
The ConnectionSettingsSection Class
The ConnectionItemElementCollection Class
The ConnectionItemElement Class
Using Configuration Validators
Using the Example Custom Configuration Section
ASP.NET applications usually rely on two of the standard sections in a Web.config file: the <appSettings> section for holding general configuration and application values, and the <connectionStrings> section that is specialized to store connection details for data providers such as database servers.
However, custom configuration sections are useful where an application requires complex sets of configuration data that the .NET configuration system can expose, and automatically validate. It is easy to create custom configuration sets and the related classes that expose this data.
When it comes to actually managing configuration values, the common approach is to simply update and deploy the Web.config file to all servers running the application. However, the ASP.NET configuration system provides no central mechanism for controlling the values in a Web.config file, which means that – if you have multiple servers such as a Web farm – the Web.config files can become unsynchronized. For example, local operators can change values in Web.config on specific machines.
Group Policy provides a mechanism for centrally managing features of the operating system and the applications that run upon it. You can take advantage of Group Policy to create a mandatory and centrally managed environment for configuration settings for your Web applications by creating a custom Group Policy Object (GPO) and building Group Policy awareness into your configuration code.
In these articles, you will see an example of a custom configuration section and the use of a Group Policy aware configuration system. Of course, you can create Group Policy aware code that reads configuration information from the <appSettings> and <connectionStrings> sections of Web.config using the technique shown in these articles. Alternatively, as demonstrated, you can combine the custom configuration provider and Group Policy aware techniques so that the configuration classes that expose the custom configuration information are themselves Group Policy aware.
You can use the standard <appSettings> and <connectionStrings> sections of Web.config to provide configuration information for ASP.NET applications and Web Services. For generic settings specific to your application, you can populate the <appSettings> section with a series of <add> elements that specify a particular setting name and its value:
<appSettings>
<add key="myFirstValue" value="2" />
<add key="mySecondValue" value="five" />
</appSettings>
You then access these settings through the AppSettings collection of the WebConfigurationManager class:
// in C#:
Int32 firstValue
= Int32.Parse(WebConfigurationManager.AppSettings["myFirstValue"]);
String secondValue
= WebConfigurationManager.AppSettings["mySecondValue"];
' in Visual Basic.NET:
Dim firstValue As Int32 _
= Int32.Parse(WebConfigurationManager.AppSettings("myFirstValue"))
Dim secondValue As String _
= WebConfigurationManager.AppSettings("mySecondValue")
However, there is no facility to map these settings to a specific schema. Therefore, the configuration system cannot automatically verify the presence or absence of a value, or validate the type. In addition, you must cast the returned values to the required types, because the AppSettings collection does not expose the values as specific .NET types.
Instead, if you create a custom configuration section and the corresponding handler, you can expose configuration data to your applications as a series of optional or required typed values, and have the configuration system automatically confirm the presence of required values, check for the correct data types, and validate the actual values.
You add a custom configuration section to a Web.config file by:
For example, to specify that the handler for the configuration section <CustomConnections> is the class named ConnectionSettingsSection in the namespace CustomConfigSection, and is implemented within the assembly named CustomConfigSection.dll, you would use:
<configSections>
<section name="CustomConnections"
type="CustomConfigSection.ConnectionSettingsSection,
CustomConfigSection, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null"/>
</configSections>
This is the full syntax, and – if required – you can omit the version, culture, and public key values. And, if the class is in the App_Code folder of your Web application, you also omit the assembly name, for example:
<configSections>
<section name="CustomConnections"
type="CustomConfigSection.ConnectionSettingsSection"/>
</configSections>
Then you can declare your custom configuration settings in the main body of Web.config using a hierarchy that corresponds to that defined within the section handler. For example:
<CustomConnections autoConnect="true">
<ConnectionItems deviceMode="Static">
<add connectionType="InternalPrice" price="2"/>
<add connectionType="ExternalPrice" price="5"/>
<add connectionType="WirelessPrice" price="8"/>
<add connectionType="GPRSPrice" price="15"/>
</ConnectionItems>
<defaultUser userName="John Smith" location="Block 7"/>
</CustomConnections>
The next section describes the custom handler for this configuration section, as used in the example application for this article.
This section describes the handler for the custom configuration section shown at the end of the previous section. The handler consists of:
If you look back at the contents of the custom section in Web.config (at the end of the previous section of this article), you will see how this set of classes corresponds to the hierarchy of the configuration section required for the application.
The ConnectionSettingsSection class corresponds to and exposes the contents of the <Connections> element that is the root of the custom configuration section. It is also the class specified in the <configSections> element that defines the handler for this section. The next code listing shows this class, together with the statements to reference the required .NET code namespaces and the declaration of the namespace for this and the other configuration handler classes:
using System;
using System.Configuration;
namespace CustomConfigSection
{
//-------------------------------------------------------
// maps to and exposes the <Connections> element
public class ConnectionSettingsSection : ConfigurationSection
{
[ConfigurationProperty("autoConnect", DefaultValue=false,
IsRequired=false)]
// returns the value of the optional "autoConnect" attribute
public Boolean AutoConnect
{
get { return (Boolean)this["autoConnect"]; }
}
[ConfigurationProperty("ConnectionItems")]
// returns the <ConnectionItems> element
public ConnectionItemElementCollection ConnectionItems
{
get
{
return (ConnectionItemElementCollection)(this["ConnectionItems"]);
}
}
// returns the <defaultUser> element
[ConfigurationProperty("defaultUser")]
public DefaultUserElement DefaultUser
{
get
{
return (DefaultUserElement)this["defaultUser"];
}
}
}
Notice that the class uses [ConfigurationProperty] attributes on each public property to indicate to the .NET configuration system the corresponding values it can expect to find in the configuration. For example, the AutoConnect property carries the attribute:
[ConfigurationProperty("autoConnect", DefaultValue=false, IsRequired=false)]
This [ConfigurationProperty] declaration indicates the name of the attribute (or element) in the configuration file, specifies that the attribute is optional, and that the default value if it is not present is false. If you omit the IsRequired and DefaultValue settings, the configuration file must contain this attribute or element.
The ConfigurationSection base class that the ConnectionSettingsSection custom class inherits from looks after parsing the file and collecting the values of each attribute and element. The code in the class can access these values simply by indexing into the collection of values, using syntax such as:
// in C#:
this["autoConnect"] // references the autoConnect attribute
this["defaultUser"] // references the defaultUser child element
' in Visual Basic.NET:
Me("autoConnect") ' references the autoConnect attribute
Me("defaultUser") ' references the defaultUser child element
Each property of the class exposes the content of the matching section of the configuration as a typed value or specific object type, and so the code in the property accessor must cast or convert the value obtained from the configuration file to the appropriate type. This means that, if the configuration value is invalid (for example, an Int32 setting that contains a non-numeric string), the configuration class will raise an exception when the application attempts to load the configuration.
The ConnectionSettingsSection class you have just seen exposes the ConnectionItems property, which is a reference to the child <ConnectionItems> element in the custom configuration section. The custom class ConnectionItemElementCollection manages the contents of this element, as you can see in the next listing. The class also exposes the value of the deviceMode attribute through the DeviceMode property:
// maps to and exposes the <ConnectionsItems> element
public class ConnectionItemElementCollection
: ConfigurationElementCollection
{
[ConfigurationProperty("deviceMode")]
// returns the value of the optional "deviceMode" attribute
public String DeviceMode
{
get
{
return this["deviceMode"].ToString();
}
}
// override the CreateNewElement method to create a concrete instance
// of the collection-specific ConnectionItemElement class
protected override ConfigurationElement CreateNewElement()
{
return new ConnectionItemElement();
}
// override the GetElementKey method to return the key that identifies
// the element in the keyed list so that the element can be retrieved
protected override object GetElementKey(ConfigurationElement element)
{
ConnectionItemElement e = (ConnectionItemElement)element;
return e.ConnectionType;
}
}
When inheriting from the ConfigurationElementCollection class, your code must override the two methods CreateNewElement and GetElementKey. In the CreateNewElement override, you simply return a new instance of the class that represents each of the <add> elements in your custom configuration section. In this example, the <add> element is of type ConnectionItemElement.
In the GetElementKey override, you must return the value from the child <add> element's attributes that represents the key for this collection of <add> elements. This is how the configuration system will locate a specific child element. In this example, application code will query the collection for a specific <add> element by specifying a value for the connectionType attribute (such as "InternalPrice") to obtain the price for this connection type. Therefore, the method override in this example casts the ConfigurationElement instance passed to it into an instance of the ConnectionItemElement type, and returns the value of this element's ConnectionType property.
This class represents each of the <add> child elements within the parent <ConnectionItems> element. It exposes the two attributes of each element as the properties named ConnectionType and Price. You can see in the next listing that both the connectionType and the price attributes are required for each element, although omitting IsRequired=true from the [ConfigurationProperty] attributes would have the same effect:
// maps to and exposes each <add> element
public class ConnectionItemElement : ConfigurationElement
{
[ConfigurationProperty("connectionType", IsRequired = true)]
// returns the value of the "connectionType" attribute
public String ConnectionType
{
get
{
return this["connectionType"].ToString();
}
}
[ConfigurationProperty("price", IsRequired = true,
DefaultValue=(Int32)10),
IntegerValidator(MinValue=1, MaxValue=25)]
// returns the value of the "price" attribute
public Int32 Price
{
get
{
return (Int32)this["price"];
}
}
}
Notice in the previous listing that the Price property defines a validator for the corresponding price attribute. This powerful feature allows you to apply even finer control over the acceptable values for your custom configuration sections. The example uses an IntegerValidator to specify that the value of the attribute must be between 1 and 25 inclusive using:
[ConfigurationProperty("price", IsRequired = true, DefaultValue=(Int32)10),
IntegerValidator(MinValue=1, MaxValue=25)]
Notice that the [ConfigurationProperty] attribute specifies a DefaultValue in this case, even though – because the attribute is required – the default value is never actually used. This is necessary because the configuration system will create an instance of the type specified for the DefaultValue (in this case an integer) before applying the validator. If you omit the DefaultValue attribute, and the value to validate is not a String, the validator will report an error.
The final class in the custom configuration handler is the class named DefaultUserElement that exposes the contents of the <defaultUser> element. This element is a child of the root <Connections> element, and the ConnectionSettingsSection class you saw earlier exposes this through its DefaultUser property.
The DefaultUserElement class exposes the values of the two attributes of this element, userName and location, as the two properties DefaultUserName and DefaultUserLocation. The location attribute is optional:
// maps to and exposes the values of the <defaultUser> element
public class DefaultUserElement : ConfigurationElement
{
// returns the value of the "userName" attribute
[ConfigurationProperty("userName")]
public String DefaultUserName
{
get
{
return this["userName"].ToString();
}
}
// returns the value of the optional "location" attribute
[ConfigurationProperty("location", IsRequired = false)]
public String DefaultUserLocation
{
get
{
return this["location"].ToString();
}
}
}
}
This combination of classes provides a hierarchical view of the custom configuration section, checks for the presence of required elements and attributes, substitutes default values for optional attributes, and validates the values of the price attributes. If any required element or attribute is missing, or a price attribute has an out-of-range value, the .NET configuration system will automatically raise an exception when the application attempts to load the configuration data.
Figure 1 shows the relationship between the configuration section and the classes in the custom handler you have just seen. From this, you can see how you can easily build custom configuration systems for your own applications.

Figure 1 – The relationships between the custom configuration section and the corresponding configuration handler classes
To see the custom configuration section and handler in action, the code available for this article contains a Web page that uses them to extract and display data from the Web.config file. Figure 2 shows the example application, and the results of reading the Web.config file. This example also demonstrates the Group Policy Aware configuration handler that you will see described in a subsequent article.

Figure 2 – Using the custom configuration section and handler in an ASP.NET application
The code-behind file for Default.aspx contains a handler for the button click event that displays the values exposed by the custom configuration section handler. The page references the CustomConfigSection namespace that contains the custom configuration handler classes, as well as the System.Web.Confguration namespace that contains the WebConfigurationManager class. This is the code for the button click event handler:
protected void btnGetConfig_Click(object sender, EventArgs e)
{
// Retrieve <CustomConnections> section from configuration file
CustomConfigSection.ConnectionSettingsSection configSection
= (CustomConfigSection.ConnectionSettingsSection)
WebConfigurationManager.GetSection("CustomConnections");
// Get value of "autoConnect" attribute on Connections element
lblAutoConnect.Text = configSection.AutoConnect.ToString();
// Get value of "deviceMode" attribute on ConnectionItems element
lblDeviceMode.Text = configSection.ConnectionItems.DeviceMode;
// Iterate through collection of <add> elements within ConnectionItems
foreach (CustomConfigSection.ConnectionItemElement conn
in configSection.ConnectionItems)
{
// Get value of "connectionType" and "price" attributes
lblConnectionItems.Text += conn.ConnectionType + " = "
+ conn.Price.ToString() + " ";
}
// Get value of attributes on <defaultUser> element
lblDefaultUser.Text = configSection.DefaultUser.DefaultUserName;
lblUserLocation.Text = configSection.DefaultUser.DefaultUserLocation;
}
The code starts by obtaining the custom section by calling the GetSection method of the WebConfigurationManager class, specifying the section name "CustomConnections". The entry in the <configSections> element in Web.config specifies that the handler for this section is the custom ConnectionSettingsSection class you saw in the previous sections of this article, and the code casts the returned configuration section to an instance of this type.
The .NET configuration system reads the custom configuration file section only when you call the GetSection method – therefore, if required, you can handle exceptions that the custom handler may raise if there are configuration errors.
Now the code can access the values exposed by the custom configuration handler, and display them in controls located within the page. Notice how easy it is to access values from the configuration, simply by referencing the properties exposed by the custom handler.
For example, the ConnectionItems property exposes the collection of ConnectionItemElement instances, each of which exposes the ConnectionType and Price properties. The DefaultUser property exposes an instance of the DefaultUserElement class, which itself exposes the DefaultUserName and DefaultUserLocation properties.
Developers are increasingly pressured into building applications that are more manageable. It is an accepted fact that deployment, runtime, and maintenance costs make up over 80% of the lifetime cost of an application, and any techniques that can help to minimize these costs are extremely welcome.
Managing application configuration is a major part of the deployment and runtime tasks that administrators face, and best practice in application design and development tends towards central management of configuration data, together with the ability to impose specific configurations on users and groups of machines. In the ASP.NET arena, being able to control centrally configuration for multiple servers, such as a Web farm, reduces the cost and complexity of updating and managing Web.config files.
This series of articles describes how you can create custom configuration sections and providers that automatically verify the presence of configuration data matching a required schema, and expose this data through objects and properties that make it easy to consume the data within applications.
Secondly, the articles describe how you can use Windows Group Policy to centrally manage configuration information, and impose specific values on all machines and users. By using these techniques within a custom configuration provider, you can easily build Group Policy aware configuration systems that automatically provide the advantages of centralized administration and control.
Finally, to demonstrate how these techniques are used in the "real world", the articles describe how version 3.0 of Microsoft's Enterprise Library provides the same kinds of capabilities through its own Manageable Configuration Provider mechanism.
©2007 Alex Homer, Stonebroom Limited, http://www.stonebroom.com