ASP.NET Configuration and Group Policy, Part 1: Creating and Using Custom Configuration Sections

SP.NET applications usually rely on two of the standard sections in a Web.config file: the section that holds general configuration and application values, and the section specialized for storing connection details for data providers such as database servers.

However, custom configuration sections are useful when an application requires complex sets of configuration data. The .NET configuration system can both expose and automatically validate these. 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’re running 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.

Fortunately, 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 using a custom configuration section and a Group Policy aware configuration system. Of course, you can create Group Policy aware code that reads configuration information from the and 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.

Custom Configuration Sections
You can use the standard and 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 section with a series of elements that specify a particular setting name and its value:

                

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.

If you instead create a custom configuration section and a 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.

Defining and Handling Custom Configuration Sections
You add a custom configuration section to a Web.config file by:

  • Specifying the class or assembly that implements the handler for the section in the section of Web.config
  • Inserting the matching configuration section and values into the body of the Web.config file

For example, to specify that the handler for the configuration section is the class named ConnectionSettingsSection in the CustomConfigSection namespace, and is implemented within the assembly named CustomConfigSection.dll, you would use:

        

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:

        
Author’s Note: For a full description of the

element syntax, see http://msdn2.microsoft.com/en-us/library/ms228245.aspx. Note that the type attribute value must be on a single line.

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:

                                                  

The next section describes the custom handler for this configuration section, as used in the example application for this article.

An Example of a Custom Configuration Section
This section describes the handler for the custom configuration section shown at the end of the previous section. The handler consists of:

  • A class named ConnectionSettingsSection that inherits from the .NET ConfigurationSection class. This class exposes:
    • The autoConnect attribute value
    • The collection of elements as an instance of the custom class named ConnectionItemElementCollection
    • The element as an instance of the custom class named DefaultUserElement
  • A class named ConnectionItemElementCollection that inherits from the .NET ConfigurationElementCollection class. This class exposes the deviceMode attribute value, and overrides the CreateNewElement and GetElementkey methods of the ConfigurationElementCollection base class.
  • A class named ConnectionItemElement that inherits from the .NET ConfigurationElement class. This class exposes the connectionType and price attribute for an element within the parent element.
  • A class named DefaultUserElement that inherits from the .NET ConfigurationElement class. This class exposes the userName and location attribute values from the element.

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’ll see how this set of classes corresponds to the hierarchy of the configuration section required for the application.

The ConnectionSettingsSection Class
The ConnectionSettingsSection class corresponds to and exposes the contents of the element that is the root of the custom configuration section. It is also the class specified in the element that defines the handler for this section. The following code shows this class, along 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  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  element         public ConnectionItemElementCollection ConnectionItems       {         get         {           return (ConnectionItemElementCollection)             (this["ConnectionItems"]);         }       }          // returns the  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 the attribute or element.

In VB.NET, the equivalent syntax is:

    _ 
Author’s Note: In VB.NET you must use the line continuation character (the underscore) after the attribute so that it prefaces the Public ReadOnly Property declaration.

The ConfigurationSection base class that the ConnectionSettingsSection custom class inherits from handles parsing the file and storing the values of each attribute and element in a collection. The class code can then access the values simply by indexing into the collection, using syntax such as:

   // in C#:   this["autoConnect"]   // the autoConnect attribute   this["defaultUser"]   // the defaultUser child element      ' in Visual Basic.NET:   Me("autoConnect")      ' the autoConnect attribute   Me("defaultUser")      ' the defaultUser child element

Each class property 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.

Author’s Note: You can also control settings by applying validator attributes to the properties that expose the configuration values, as you’ll see later in this article with the ConnectionItemElement class.

The ConnectionItemElementCollection Class
The ConnectionSettingsSection class you have just seen exposes the ConnectionItems property, which is a reference to the child element in the custom configuration section. The custom class ConnectionItemElementCollection manages the contents of this element, as you can see in the code below. The class also exposes the value of the deviceMode attribute through the DeviceMode property:

   // maps to and exposes the  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 elements in your custom configuration section. In this example, the element is of type ConnectionItemElement.

In the GetElementKey override, you must return the value from the child element’s attributes that represents the key for this collection of 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 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.

The ConnectionItemElement Class
This class represents each of the child elements within the parent element. It exposes the two attributes of each element as the properties named ConnectionType and Price. You can see in the following code 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  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"];          }      }   }

Using Configuration Validators
Notice in the preceding code that the Price property defines a validator for the corresponding price attribute. This powerful feature lets you 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.

Author’s Note: For more information on using validators within configuration handlers, see http://msdn2.microsoft.com/en-us/library/system.configuration.configurationelement.aspx.

The DefaultUserElement Class
The final class in the custom configuration handler is the class named DefaultUserElement that exposes the contents of the element. This element is a child of the root element. The ConnectionSettingsSection class you saw earlier exposes this element 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     // 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();         }
?
Figure 1. Relationships: The figure shows the relationships between the custom configuration section and the corresponding configuration handler classes.
} }

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.

Using the Example Custom Configuration Section
To see the custom configuration section and handler in action, the downloadable code available with this article contains a web page that extracts and displays custom data from the Web.config file. Figure 2 shows the example application displaying values read from 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. Sample Application Display: The figure shows an ASP.NET application displaying values from the custom configuration section using the handler.

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. Here’s the code for the button Click event handler:

   protected void btnGetConfig_Click(      object sender, EventArgs e)   {      // Retrieve  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  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  element      lblDefaultUser.Text =          configSection.DefaultUser.DefaultUserName;      lblUserLocation.Text =          configSection.DefaultUser.DefaultUserLocation;   } 

The preceding code first obtains the custom section by calling the WebConfigurationManager class’s GetSection method, specifying the section name CustomConnections. The entry in the 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 when configuration errors occur.

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.

Increasing Manageability
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 percent 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 toward 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 increases application manageability by describing how to create custom configuration sections and providers that automatically verify the presence of configuration data matching a required schema, and how to expose that data through objects and properties that simplify consuming the data from your applications.

The next installment in this article series discusses creating and using Group Policy aware providers in ASP.NET.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: