We have all come to the same situation when developing ASP.NET web applications, our web.config file gets cluttered by all sorts of ungrouped settings in the default appSettings section. That default section only offers the name-value pairs so if you need more attributes for a single setting then you have no immediate alternative other than splitting it accross several name-value pairs.
In ASP.NET you can actually write your own web configuration section handlers so that you can group your settings in a propper way. You are (hopefully) already using Object Oriented Programming, so why not use similar principles for your configuration settings?
In this article I will show you how to write a relatively simple custom configuration section for your web.config file in a few easy steps. I will show you this using a useful example rather than a generic, good-for-nothing example. In ASP.NET you can use the mailSettings section in the web.config but there you can only specify a single SMTP (mail) server. Our case study will implement a custom configuration section that supports a default SMTP server setting and several optional Fallback SMTP servers. It is beyond the scope of this article to actually implement the logic behind the mail settings, so I will limit myself to the task at hand: a custom mail settings configuration section. Furthermore we will be using the Declarative Model
Custom Mail Settings Configuration Section
In order to implement a custom configuration settings section for your web configuration file we will need to implement these:
- A custom section handler
- A custom settings container contained within the custom section
- Declare our custom section in the web configuration file
- Add our custom section with data in the web configuration file
Additionally you may also want to have:
- An XML schema for your configuration section, hopefully for Intellisense in Visual Studio
- The capability to encrypt your configuration section if it contains sensitive data
Our XML section markup
The first thing we can do is worry about the declaration of our custom section in web.config so that the ASP.NET configuration handlers know how to parse our custom section. We will call our configuration section flexMailSettings which will be the root element of our custom section. I decided that I will implement my custom handler in the Coralys.Web.Configuration namespace to follow the same hierarchy as the .NET framework. Additionally, the entire handler will be implemented in a standalone DLL (Class Library Project) named "Coralys.Web.Config.dll".
Declare your custom section handler
Open up your web.config file and add the section declaration within the <configuration><configSections> element. It would look like this (the rest omitted for clarity):
Notice the <section> element, it has a Name attribute which basically names the root element of our custom configuration section. The shown type declaration attribute is in the "Namespace.Class, AssemblyName" notation because it resides in a separate assembly (I don't like using App_Code).
Place custom section with data
After exploring what sort of settings we need to store, we define how we want our XML configuration section to look like. The settings we want to store will be the attributes of XML elements. In our case we want multiple SMTP servers to be defined so we need a collection as well. For clarity we will use an XML element to group the various SMTP server entries. Our custom section will look like this in order to fulfill our requirements:
We want the host, username and id attributes to be compulsory, whereas the others are optional. The defaultCredential attribute is false by default so we only set it to true for the server and credentials we want to use as default SMTP authentication credentials (see System.Net namespace for mail functionality). With this setup we have multiple fallback servers and also indicate the maximum number of tries before trying a fallback server.
A schema for our custom section
Sometimes you want to have the syntax of your XML section properly defined and hopefully even get Intellisense functionality within Visual Studio.
First copy the entire "flexMailSettings" XML element from the previous section and paste it in another file in your Visual Studio project. Then select XML | Create Schema from the toolbar. In this case we get this XML Schema (XSD file):
Now, getting Intellisense in Visual Studio is not so easy. Apparently putting the XSD file in your project isn't enough. If you really want to make sure you get Intellisense, then put or install the XML schema file in C:\Program Files (x86)\Microsoft Visual Studio 9.0\Xml\Schemas directory. The actual path depends on where you installed Visual Studio and your VS version.
Then open up the catalog.xml file in that same directory and add an <Association> element that adds your schema.
The actual handler code
Having examined what settings we want to specify in our configuration section and added all the XML markup in the web configuration file, we need to proceed to actually implement our custom configuration handler.
Handling individual server entries: SmtpSetting
Following a bottoms up approach we define first a handler class that takes care of interpreting each of the individual SMTP server entries that make up the collection of fallback server entries. Our SmtpSetting class extends ConfigurationElement and it handles each of the <add> fallback elements in the smtpServers parent element which groups all the fallbacks.
Matching the definitions in the XML schema from the previous section, I have added meta data attributes to indicate whether the attribute is optional (IsRequired=false) or required. I also added some metadata validators and default values. Should the values given in the web.config not match the provided constraints, an error would be produced.
The collection of Fallback SMTP servers: SmtpSettingsCollection
The class presented in the previous section represents a single entry of many in a list or collection, it would be best represented by grouping them as shown in the XML markup of our custom section. For that reason we implement a simple collection of fallback SMTP servers, in XML we name this collection element 'smtpServers'. In our handler this collection is implemented as follows:
I used an indexer property of type string because each of our SmtpSetting items (the <add> elements) is (or should be) uniquely identified by an ID property. This ID property is a friendly name that we can display on the listing of fallback SMTP servers.
Custom section root element
Not last but certainly not least, we have the root element (flexMailSettings) of our XML custom section. This root element holds our collection of SMTP server (smtpServers element) as well as has its own attribute to specify a setting that applies to all fallback SMTP servers, this is the maxTries attribute. The maxTries attribute specifies the maximum number of tries on any given SMTP server before trying the next one on the list.
With it we also have access to the collection of servers using the SmtpServers property.
A helper class
I chose to implement a helper class for my custom configuration section. This helper class gives me simplified access to my configuration section without the hassle of having to repeat code.
Using the custom section
To recapitulate, I implemented all the code necessary to have a custom section handler in a separate assembly (Coralys.Web.Configuration.dll) which not surprisingly contains my classes in the namespace of the same name. I compiled the assembly and in my web application project I added a reference to that assembly.
Then I added the declaration of my custom configuration section in web.config. This tells .NET how to handle (by means of a reference to an assembly and a handler class) to interpret my new section. I also added the actual custom section XML markup with the data I require.
To actually use my section I make use of the helper class I wrote. The code below shows an example of how to access the various settings in my custom section. The helper class also offers the possibility of updating those settings and saving them to the configuration file. This update functionality is useful if you have an administration page in a secure area of your site.
Encrypting the custom section
Last but certainly not least, you should be aware that it is very likely that this configuration section would have sensitive information such as the email account names and passwords if the SMTP server requires authentication. For that reason it is advised to encrypt that section using aspnet_regiis. In that case you would need to specify "flexMailSettings" as the section name to encrypt (or decrypt).
There is a quirk though even though the ASP.NET process can find the handler for the section at runtime, it is impossible to encrypt the section using the aspnet_regiis utility. An exhaustive search on the internt revealed a defect in this Microsoft utility. For some obscure reason when encrypting a custom section it forgets to look into the web application's bin directory. As a result it can't find the handler code and it fails at encrypting.
To encrypt the custom section (in cases where it is a referenced external assembly) you will first need to copy your DLL to the .NET Framework's directory (where aspnet_regiis resides) prior to encrypting. Then encryption will succeed because it finds the assembly. You can remove your assembly from the .NET Framework's directory afterwards (and you should) as it is not needed there for regular operation.