Advertisement

How to Add Custom Configuration Settings for an (ASP).NET Application

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

Since its release, ASP.NET applications and components have looked to the web.config file to load any settings they need to function. However, adding custom settings to add flexibility and robustness to an application or component isn't as straight forward as most would like. This article teaches you how to write the necessary classes to handle XML configuration elements and use the settings they contain within your code.

Republished Tutorial

Every few weeks, we revisit some of our reader's favorite posts from throughout the history of the site. This tutorial was first published in November, 2012.

The .NET Framework provides a wide variety of settings that can be configured within web.config to modify the behavior of one or more built-in components within the application. For some developers, sticking solely with the settings provided by the .NET Framework is sufficient. But many more developers find they need to control a broader collection of settings - either for components (written by themselves or a third party), or simply a set of values they find themselves using throughout their application.

The web.config file does allow you to set custom settings with the <appSettings/> element, but it doesn't allow anything other than simple key/value pairs. The following XML element is an example of a setting contained within <appSettings/>:

<add key="myKey" value="myValue"/>

Key/Value settings certainly can be helpful in many circumstances, but <appSettings/> settings simply aren't flexible enough for robust or complex components or settings.

Thankfully, Microsoft enables developers to write classes that add programmatic access to custom configuration settings contained within web.config.


The Configuration Section

Settings within web.config are categorized into configuration sections. For example, the settings contained within the <system.web/> section pertains to ASP.NET settings for your application. You can change the authentication scheme of your app, as well as add or remove HTTP handlers to perform specific functions for specific file types. The <system.webServer/> section allows you to control many of IIS7's settings without having direct access to IIS7.

A configuration section is required of all settings not contained within the <appSettings/> element. So it's a good idea to design the XML structure of your configuration settings before writing any code.

The configuration used as an example in this tutorial is for a component that retrieves RSS or Atom feeds. It doesn't do any parsing, as that is beyond the scope of this tutorial. Instead of hard coding the list of feeds to retrieve, the component looks to its configuration to contain the names and URLs of the feeds to retrieve. The component is called FeedRetriever, and the desired XML structure of its configuration looks like this:

<feedRetriever>
    <feeds>
        <add name="Nettuts+" url="http://feeds.feedburner.com/nettuts" cache="false"/>
        <add name="Jeremy McPeak" url="http://www.wdonline.com/feeds/blog/rss/" />
        <add name="Nicholas C. Zakas" url="http://feeds.nczonline.net/blog/" />
    </feeds>
</feedRetriever>

The <feedRetriever/> element defines by the configuration section. As a general rule, a configuration section should share the name of the the component it is designed for. The <feedRetriever/> elements only child is the <feeds/> element. Think of this element as a collection of feeds because it contains several <add/> elements (think of the Add() method that most collection objects have). The choice of using an element named "add" may seem strange at first, but the <add/> element is used throughout the majority of built-in configuration sections. So using it here simply follows the design practices put forth by Microsoft.

These <add/> elements use the name, url, and cache attributes to set certain settings for each feed. Naturally, the name and url attributes are required, but the cache attribute is not, and should default as true.

The above configuration is simple. The <feedRetriever/> element could be modified to contain another child, called <globalSettings/>, to contain settings that would apply to all feeds. The <add/> elements could also use additional attributes, such as cacheTime and requestFrequency, to control how long a feed is cached and how often it is requested from the remote host. The only limit to the extensibility and configurability is your imagination.


Writing the Configuration Handler

After designing the XML structure, the next step is to write a configuration handler to process the settings defined in the XML. The handler is primarily a class that inherits from System.Configuration.ConfigurationSection, but it also incorporates the use of other classes - such as classes that derive from System.Configuration.ConfigurationElement and System.Configuration.ConfigurationElementCollection.

Classes based on ConfigurationElement represent individual elements; it is the building block of a configuration section. Types that derive from ConfigurationElementCollection simply represent elements that contain more than one type of element. From the configuration listed above, the <feeds/> element is represented by a class that derives from ConfigurationElementCollection, and the <add/> elements are represented by a ConfigurationElement-based class.


Representing the <add/> Element

You'll start with the <add/> element by representing it with a class called FeedElement (derived from ConfigurationElement). This class, and future configuration-related classes, reside in the FeedRetriever.Configuration namespace.

Every ConfigurationElement object functions as an indexer for its internal collection of property values. It is this internal collection, along with .NET attributes, that enables you to map the <add/> element's attributes to the properties of the FeedElement class.

The following code is the complete code for the FeedElement class:

public class FeedElement : ConfigurationElement
{
    [ConfigurationProperty("name", IsKey = true, IsRequired = true)]
    public string Name
    {
        get { return (string)this["name"]; }
        set { this["name"] = value; }
    }

    [ConfigurationProperty("url", IsRequired = true, DefaultValue = "http://localhost")]
    [RegexStringValidator(@"https?\://\S+")]
    public string Url
    {
        get { return (string)this["url"]; }
        set { this["url"] = value; }
    }

    [ConfigurationProperty("cache", IsRequired = false, DefaultValue = true)]
    public bool Cache
    {
        get { return (bool)this["cache"]; }
        set { this["cache"] = value; }
    }
}

The ConfigurationElement class serves as an indexer to an underlying collection of configuration properties (hence the indexer notation of this[keyValue]). By using the this keyword and accessing the underlying property with a string key, you can get and set the property's value without needing a private field to contain that data. The underlying property collection stores data as type Object; therefore, you have to cast the value as the appropriate type if you want to do anything with it.

The properties that represent XML attributes are decorated with ConfigurationPropertyAttribute attributes. The first parameter of of the ConfigurationPropertyAttribute attribute is the name of the XML attribute found within the <add/> element. Following the first parameter are a set of any number of named parameters. The following list is a complete list of possible parameters:

  • DefaultValue - Gets or sets the default value for the decorated property. This parameter
    is not required.
  • IsDefaultCollection - Gets or a sets a Boolean value indicating whether the property
    is the default property collection for the decorated property. This parameter is
    not required, and the default is false.
  • IsKey - Gets or sets a Boolean value indicating whether this property is a key property
    for the decorated element property. This parameter is not required, and its default
    value is false.
  • IsRequired - Gets or sets a Boolean value indicating whether the decorated element
    property is required. This parameter is not required, and its default value is false.

The default value of "http://localhost" for the Url property is not an error. The .NET Framework also grants you the ability to decorate the properties with validator attributes - such as the RegexStringValidatorAttribute decorating the Url property. This validator takes the value of the Url property and validates it against the regular expression provided to the attribute; however, it also validates the Url property before it contains the data from the XML element. The default value of the Url property is an empty string when a FeedElement object is first created. An empty string does not validate against the provided regular expression, so the validator throws an ArgumentException before any data is loaded from the XML file.

There are two possible workarounds for this problem. The first approach modifies the regular expression to allow empty strings. The second approach assigns a default value to the property. It does not matter in this particular case. Even with a default value, the url attribute is still a required attribute in the <add/> element - the application throws a ConfigurationErrorsException if an <add/> element does not have a url attribute.

There are several other validator attributes in the System.Configuration namespace to validate data assigned to properties and the XML attributes they map to. The following lists all of the validator attributes within the System.Configuration namespace:

With the exception of the CallbackValidatorAttribute, you do not have to create corresponding validator objects to use in conjunction with the validator attributes. The .NET runtime creates the appropriate validator objects for you, and the attributes contain the needed parameters to configure the validator objects.

This small bit of code is all that is required to programmatically represent individual <add/> elements. The next step is to write a class that represents the <feeds/> element.


Writing an Element Collection Class

The XML representation of the <feeds/> element is that of a collection of feed elements. Likewise, the programmatic representation of the <feeds/> element is a collection of FeedElement objects. This class, called FeedElementCollection, derives from the abstract ConfigurationElementCollection class.

The ConfigurationElementCollection class contains several members, but only two are marked as abstract. Thus, the simplest ConfigurationElementCollection implementation has two methods:

  • CreateNewElement() - Creates a new ConfigurationElement object (FeedElement in this
    case).
  • GetElementKey() - Gets the element key for a specified configuration element (the
    Name property of FeedElement objects in this case).

With that in mind, view the complete code for the FeedElementCollection class below:

[ConfigurationCollection(typeof(FeedElement))]
public class FeedElementCollection : ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new FeedElement();
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((FeedElement)element).Name;
    }
}

A ConfigurationCollectionAttribute decorates this collection class. The first parameter to the attribute is a Type object - the type of the items the collection contains. In this case, it's the FeedElement type. After the type parameter are several named parameters you can pass to the attribute. These are listed below:

  • AddItemName - Sets the name of the <add/> configuration element. For example,
    setting this as "feed" would require the <add/> elements in the
    configuration to be changed to <feed/>.
  • ClearItemsName - Sets the name of the <clear/> configuration element (used
    to clear all items from the collection).
  • RemoveItemName - Sets the name for the <remove/> configuration element (used
    to remove an item from the collection).

Leaving these named parameters blank defaults them to <add/>, <clear/>, <remove/>.


Writing the FeedRetreiverSection Class

The final class, called FeedRetrieverSection, derives from ConfigurationSection and represents the <feedRetriever/> element. This is the simplest class of the configuration classes, as the only requirement it must meet is to provide programmatic access to the <feeds/> element (the FeedElementCollection).

public class FeedRetrieverSection : ConfigurationSection
{
    [ConfigurationProperty("feeds", IsDefaultCollection = true)]
    public FeedElementCollection Feeds
    {
        get { return (FeedElementCollection)this["feeds"]; }
        set { this["feeds"] = value; }
    }
}

It's one property, of type FeedElementCollection and called Feeds, is decorated with a ConfigurationPropertyAttribute - mapping it to the <feeds/> element.


Modifying web.config

With the configuration handler complete, you can add the appropriate elements to web.config. The <feedRetriever/> section can go anywhere in the file as long as it's a direct descendent of the root element (the <configuration/> element). Placing it within another configuration section results in an error.

The next step is adding a <section/> child element to <configSections/>. The <section/> element has two attributes of interest:

  • name - The name of the configuration section element. In this case, name is feedRetriever.
  • type - The qualified name of the class associated with the section, and if necessary,
    the name of the assembly the class resides in. In this case, the qualified name
    is FeedRetriever.Configuration.FeedRetrieverSection. If it resides in a separate
    assembly, the type attribute would have a value of "FeedRetriever.Configuration.FeedRetrieverSection,
    <assemblyName>", where <assemblyName> is the name of the assembly
    without the angle brackets.

The following <section/> element is what you add to a web.config file, under <configSections/>, when the configuration classes do not reside in a separate assembly (as is the case in the code download):

<section name="feedRetriever" type="FeedRetriever.Configuration.FeedRetrieverSection"/>

Now your application is properly configured to use the FeedRetrieverSection, FeedElementCollection, and FeedElement classes to grant you programmatic access to the custom settings contained within the <feedRetriever/> configuration section in web.config. So how do you access these settings from within your code?


Accessing Configuration Data from Code

The System.Configuration namespace contains a static class called ConfigurationManager. If you use the <connectionStrings/> section to house your connection strings, you are at least familiar with ConfigurationManager. It has a method called GetSection(), which accepts a string containing the name of the configuration section to retrieve. The following code demonstrates this (assume using System.Configuration is at the top of the code file):

FeedRetrieverSection config = ConfigurationManager.GetSection("feedRetriever") as FeedRetrieverSection;

The GetSection() method returns a value of type Object, so it must be cast to whatever type the handler is for that section. This code retrieves the section named feedRetriever and casts the result as FeedRetrieverSection. Once you have the object, you can start accessing configuration data programmatically.

To give you an idea of how configuration settings can be used within your component or application, the following code is a very basic implementation of the FeedRetriever component.

public class FeedRetriever
{
    public static FeedRetrieverSection _Config = 
        ConfigurationManager.GetSection("feedRetriever") as FeedRetrieverSection;

public static void GetFeeds() { foreach (FeedElement feedEl in _Config.Feeds) { // make request HttpWebRequest request = (HttpWebRequest)WebRequest.Create(feedEl.Url); HttpWebResponse response = (HttpWebResponse)request.GetResponse(); if (response.StatusCode == HttpStatusCode.OK) { string feedData = String.Empty; using (StreamReader reader = new StreamReader(response.GetResponseStream())) { feedData = reader.ReadToEnd(); } if (feedEl.Cache) { // filename of cache file string filename = String.Format("{0}_{1}.xml", feedEl.Name, DateTime.Now.Ticks); // cache file using (StreamWriter writer = new StreamWriter(@"C:\" + filename)) { writer.Write(feedData); } } } } } }

First, a static variable called _Config, of type FeedRetreiverSection, is declared and assigned a value by calling ConfigurationManager.GetSection(). Making the variable static is a design choice. By doing so, all members of the class, either instance or static, would have access to the configuration settings without having to make multiple calls to GetSection().

Once you retrieve the section handler with GetSection(), you have complete access to objects created from your handler classes. The first line of GetFeeds() is a for each loop that loops through all FeedElement objects contained with the FeedElementCollection object returned by the Feeds property. This gives you direct access to those FeedElement objects - making it easy to access each feed's name, URL, and cache settings.

During each iteration of the loop, the method makes a request using the FeedElement object's Url property. If the request results in a success, the feed's data is retrieved and stored in the feedData variable. Then the code checks the FeedElement object's Cache property to determine whether or not to cache the feed. Caching the the feed involves constructing a filename by using the FeedElement object's Name property and the current date and time. Then a StreamWriter object creates the file and writes the feed's data to it.

As you can see, using the configuration section handler classes is key to retrieving and using custom settings residing in web.config. It certainly requires more time and effort from you, but it definitely makes your application or component much easier to configure for yourself and other developers.


Sell your .NET Components on CodeCanyon!

Did you know that we have a .NET category on CodeCanyon. If you're a skilled .NET dev, why not sell your scripts/components/controls as an author, and earn 40-70% of every sale?

Advertisement