SP.NET applications usually rely on two of the standard sections in a Web.config file: the
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
Custom Configuration Sections
You can use the standard
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
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
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
// 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
In the GetElementKey override, you must return the value from the child
The ConnectionItemElement Class
This class represents each of the
// 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
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
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.