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

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

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.

Tutorial Details

  • Technology: .NET (C#)
  • Difficulty: Advanced
  • Estimated Completion Time: 1 hour

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 <a title="MSDN Documentation for System.Configuration.ConfigurationSection"
href=”http://msdn.microsoft.com/en-us/library/system.configuration.configurationsection.aspx”
target=”_blank”>System.Configuration.ConfigurationSection, but it also incorporates
the use of other classes ñ such as classes that derive from <a title="MSDN Documentation for the System.Configuration.ConfigurationElement class."
href=”http://msdn.microsoft.com/en-us/library/system.configuration.configurationelement.aspx”
target=”_blank”>System.Configuration.ConfigurationElement and <a title="MSDN Documentation for the System.Configuration.ConfigurationElementCollection class."
href=”http://msdn.microsoft.com/en-us/library/system.configuration.configurationelementcollection.aspx”
target=”_blank”>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 <a title="MSDN – Documention for the ConfigrationPropertyAttribute class."
href=”http://msdn.microsoft.com/en-us/library/system.configuration.configurationpropertyattribute.aspx”
target=”_blank”>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 <a title="MSDN Documentation for the RegexStringValidatorAttribute class."
href=”http://msdn.microsoft.com/en-us/library/system.configuration.regexstringvalidatorattribute.aspx”
target=”_blank”>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:

  • <a title="MSDN Documentation for the CallBackValidatorAttribute class." href="http://msdn.microsoft.com/en-us/library/system.configuration.callbackvalidatorattribute.aspx"
    target=”_blank”>CallbackValidatorAttribute ñ Provides an association between
    a <a title="MSDN Documentation for the CallbackValidator class." href="http://msdn.microsoft.com/en-us/library/system.configuration.callbackvalidator.aspx"
    target=”_blank”>CallbackValidator object and the code to validate ñ allows
    dynamic validation for a configuration value.
  • <a title="MSDN Documentation for the IntegerValidatorAttribute class" href="http://msdn.microsoft.com/en-us/library/system.configuration.integervalidatorattribute.aspx"
    target=”_blank”>IntegerValidatorAttribute ñ Validates using an <a title="MSDN Documentation for the IntegerValidator class."
    href=”http://msdn.microsoft.com/en-us/library/system.configuration.integervalidatorattribute.aspx”
    target=”_blank”>IntegerValidator object to determine if the configuration
    value falls within or outside a specific range.
  • <a title="MSDN Documentation for the LongValidatorAttribute class." href="http://msdn.microsoft.com/en-us/library/system.configuration.longvalidatorattribute.aspx"
    target=”_blank”>LongValidatorAttribute ñ Validates using a <a title="MSDN Documentation for the LongValidator class."
    href=”http://msdn.microsoft.com/en-us/library/system.configuration.longvalidator.aspx”
    target=”_blank”>LongValidator object to determine if the configuration value
    falls within or outside a specific range.
  • <a title="MSDN Documentation for the PositiveTimeSpanValidatorAttribute class."
    href=”http://msdn.microsoft.com/en-us/library/system.configuration.positivetimespanvalidatorattribute.aspx”
    target=”_blank”>PositiveTimeSpanValidatorAttribute ñ Validates using a <a title="MSDN Documentation for the PositiveTimeSpanValidator class."
    href=”http://msdn.microsoft.com/en-us/library/system.configuration.positivetimespanvalidator.aspx”
    target=”_blank”>PositiveTimeSpanValidator object for positive TimeSpan configuration
    values.
  • <a title="MSDN Documentation for the RegexStringValidatorAttribute class." href="http://msdn.microsoft.com/en-us/library/system.configuration.regexstringvalidatorattribute.aspx"
    target=”_blank”>RegexStringValidatorAttribute ñ Validates using a <a title="MSDN Documentation for the RegexStringValidator class."
    href=”http://msdn.microsoft.com/en-us/library/system.configuration.regexstringvalidator.aspx”
    target=”_blank”>RegexStringValidator object to determine if the configuration
    value adheres to the the regular expression.
  • <a title="MSDN Documentation for the StringValidatorAttribute class." href="http://msdn.microsoft.com/en-us/library/system.configuration.stringvalidatorattribute.aspx"
    target=”_blank”>StringValidatorAttribute ñ Validates using a <a title="MSDN Documetation for the StringValidator class."
    href=”http://msdn.microsoft.com/en-us/library/system.configuration.stringvalidator.aspx”
    target=”_blank”>StringValidator object to ensure the configuration value
    meets certain criteria ñ such as string length and invalid characters.
  • <a title="MSDN Documentation for the SubclassTypeValidatorAttribute class." href="http://msdn.microsoft.com/en-us/library/system.configuration.subclasstypevalidatorattribute.aspx"
    target=”_blank”>SubclassTypeValidatorAttribute ñ Validates using a <a title="MSDN Documentation for the SubclassTypeValidator class."
    href=”http://msdn.microsoft.com/en-us/library/system.configuration.subclasstypevalidator.aspx”
    target=”_blank”>SubclassTypeValidator object to determine if the configuration
    value derives of a given type.
  • <a title="MSDN Documentation for the TimeSpanValidatorAttribute class." href="http://msdn.microsoft.com/en-us/library/system.configuration.timespanvalidatorattribute.aspx"
    target=”_blank”>TimeSpanValidatorAttribute ñ Validates using a <a title="MSDN Documentation for the TimeSpanValidator class."
    href=”http://msdn.microsoft.com/en-us/library/system.configuration.timespanvalidator.aspx”
    target=”_blank”>TimeSpanValidator object to determine if the configuration
    value is falls within or outside a specific range.

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 <a title="MSDN Documentation for the ConfigurationCollectionAttribute class." href="http://msdn.microsoft.com/en-us/library/system.configuration.configurationcollectionattribute.aspx"
target=”_blank”>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
foreach 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?



Leave a Reply

Your email address will not be published. Required fields are marked *