Building a Custom XML Configuration System for ASP.NET – Part 1

Having a powerful and extensible configuration system is a common facet of any application. Almost all the times this configuration system is based on XML files. In .NET world, there is a built-in mechanism for configuration management for application configuration files (App.Config or Web.Config) that lets you add some custom sections as you wish and work with them via a rich set of APIs.

But the problem is that this mechanism is limited and for some applications with more configuration prerequisites it can’t be very proper. So many of the real world professional .NET applications (especially ASP.NET web applications) have their own configuration system based on XML .config files. In such systems you parse XML data and retrieve whatever you want manually.

This is the common method based on XML manipulation API in .NET 1.x, 2.0 and 3.0. Here in a new post series where I want to talk about building such a mechanism with older API and also with XLinq API in two separate posts to show the power of XLinq in .NET Framework 3.5.

The current post is about building this XML configuration system with older XML classes provided in the .NET Framework.

Overview

The process of writing such a configuration system consists of a few steps that are all simple and straightforward.

First you need to define properties that are mostly related to configuration data in your file. Then you need to load an XML data into memory and write appropriate methods to parse the data and set the properties.

Beside this, you also need to apply a caching mechanism to improve the performance of your code because usually XML configuration classes are called frequently and it doesn’t sound like a good idea to run all these operations whenever the code is called.

Now let’s take a closer look at this process.

Create an XML Configuration File

Obviously the first step is to create an XML file that keeps your configuration data. You can give any special structure to this file as you like. Here I create a sample file that will cover all the main concepts that I want to discuss.

<?xml version="1.0"?>

<Keyvan>

  <Site

    domainName="Site.Com"

    siteTitle="Site Title"

    siteDescription="A description for your site."

    siteKeywords=".net, asp.net, C#, keyvan, nayyeri"

    useSsl = "true"

    sitePageSize="10"

    />

  <Data operationBatchSize="200" />

  <Tasks>

    <Task name="Emailing"

          type="XmlConfigurationSample.Emailing, XmlConfigurationSample"

          interval="600000"

          enabled="true"

          priority="1" />

  </Tasks>

</Keyvan>

This file keeps some site related information as well as an element for bulk data operations and a section for scheduled tasks. You can use this section in conjunction with your task scheduling system. As example is our Abidar task scheduler for the ASP.NET that can simply work with such a configuration system. I had written about Abidar with sample code before.

Fields and Properties

The first step is to create a class. I create this class and name it Config1. Later I will work on Config2 to load my configuration with XLinq.

I need to create some fields and properties for this class. I require two fields. One fields is an XmlDocument that keeps the data for my XML configuration file and will be used throughout this process to work with the configuration file. The other field is a string cache key that will be used to store my configuration data into cache to improve the performance.

I also need to create some read only properties for the information stored in my configuration file. So I need to create some string and integer properties as you see in the below code.

using System;

using System.Data;

using System.Configuration;

using System.Linq;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Xml.Linq;

using System.Xml;

using System.Web.Caching;

 

namespace XmlConfigurationSample.Code

{

    public class Config1

    {

        #region Fields

 

        private XmlDocument _xmlDoc = null;

        private const string _cacheKey = "KeyvanConfiguration";

 

        #endregion

 

        #region Properties

 

        #region Site

 

        private string _siteDomainName = "site.com";

        public string SiteDomainName

        {

            get

            {

                return this._siteDomainName;

            }

        }

 

        private string _siteTitle = "Site Title";

        public string SiteTitle

        {

            get

            {

                return this._siteTitle;

            }

        }

 

        private string _siteDescription = string.Empty;

        public string SiteDescription

        {

            get

            {

                return this._siteDescription;

            }

        }

 

        private int _sitePageSize = 10;

        public int SitePageSize

        {

            get

            {

                return this._sitePageSize;

            }

        }

 

        #endregion

 

        #region Data

 

        private int _operationBatchSize = 100;

        public int OperationBatchSize

        {

            get

            {

                return this._operationBatchSize;

            }

        }

 

        #endregion

 

        #endregion

    }

}

Public Constructor

My configuration class also needs a public constructor with a custom implementation. In this constructor I get an XmlDocument as parameter to set _xmlDoc field. Note that LoadValuesFromXmlDocument is a private method that I will cover later.

#region Public Constructors

 

public Config1()

{

}

 

public Config1(XmlDocument doc)

{

    this._xmlDoc = doc;

    try

    {

        LoadValuesFromXmlDocument();

    }

    catch

    {

        // TODO: Handle Exception

    }

}

 

#endregion

Public Methods

There are also some public methods that are very important when dealing with the configuration system. The first and most important method is static Load method. This public function returns an instance of the Config1 class. Its internal working consists of loading an instance of the configuration class with all the configuration data and returning it back to the caller. This is the entrance point for users when using the configuration system.

public static Config1 Load()

{

    Config1 configuration = new Config1();

 

    if (HttpRuntime.Cache.Get(_cacheKey) != null)

    {

        configuration = HttpRuntime.Cache.Get(_cacheKey) as Config1;

    }

    else

    {

        string path = HttpContext.Current.Server.MapPath("~/config/keyvan.config");

 

        XmlDocument doc = new XmlDocument();

        doc.Load(path);

 

        configuration = new Config1(doc);

 

        HttpRuntime.Cache.Insert(_cacheKey, configuration, new CacheDependency(path),

            DateTime.MaxValue, new TimeSpan(12, 0, 0));

    }

 

    return configuration;

}

But how does it work? First it checks if there is an existing configuration object stored in the cache. If there is any object, then it will return the object and this has a huge impact on the performance. But if it doesn’t exist in the cache, then it loads XML configuration content into an XmlDocument and passes this object to the public constructor to load values. It finally inserts this object into the cache.

Beside this, there are also three other helper methods for the configuration system that can either be used by the configuration class to load values or be called by other classes to get access to a part of the configuration.

There is a GetTasksNodes function that returns an XmlNodeList as well as GetConfigSection and GetConfigSections functions that return an XmlNode and XmlNodeList by getting an XPath query respectively. These latter functions are used by other configuration methods internally.

public XmlNodeList GetTasksNodes()

{

    return GetConfigSections("Keyvan/Tasks/Task");

}

 

public XmlNode GetConfigSection(string nodePath)

{

    return this._xmlDoc.SelectSingleNode(nodePath);

}

 

public XmlNodeList GetConfigSections(string nodesPath)

{

    return this._xmlDoc.SelectNodes(nodesPath);

}

Private Methods

But the last step of the development is, implementing a method that loads values from the internal XmlDocument to properties of the configuration class. This is as easy as some regular XML manipulation code.

#region Private Methods

 

private void LoadValuesFromXmlDocument()

{

    // Site

    XmlNode node = GetConfigSection("Keyvan/Site");

    XmlAttributeCollection attributes = node.Attributes;

 

    XmlAttribute attribute = attributes["domainName"];

    if (attribute != null)

        this._siteTitle = attribute.Value;

 

    attribute = attributes["siteTitle"];

    if (attribute != null)

        this._siteTitle = attribute.Value;

 

    attribute = attributes["siteDescription"];

    if (attribute != null)

        this._siteDescription = attribute.Value;

 

    attribute = attributes["sitePageSize"];

    if (attribute != null)

        this._sitePageSize = int.Parse(attribute.Value);

 

    // Data

    node = GetConfigSection("Keyvan/Data");

    attributes = node.Attributes;

 

    attribute = attributes["operationBatchSize"];

    if (attribute != null)

        this._operationBatchSize = int.Parse(attribute.Value);

}

 

#endregion

Wrapping Up

After all, my configuration class is something like this:

using System;

using System.Data;

using System.Configuration;

using System.Linq;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Xml.Linq;

using System.Xml;

using System.Web.Caching;

 

namespace XmlConfigurationSample.Code

{

    public class Config1

    {

        #region Fields

 

        private XmlDocument _xmlDoc = null;

        private const string _cacheKey = "KeyvanConfiguration";

 

        #endregion

 

        #region Properties

 

        #region Site

 

        private string _siteDomainName = "site.com";

        public string SiteDomainName

        {

            get

            {

                return this._siteDomainName;

            }

        }

 

        private string _siteTitle = "Site Title";

        public string SiteTitle

        {

            get

            {

                return this._siteTitle;

            }

        }

 

        private string _siteDescription = string.Empty;

        public string SiteDescription

        {

            get

            {

                return this._siteDescription;

            }

        }

 

        private int _sitePageSize = 10;

        public int SitePageSize

        {

            get

            {

                return this._sitePageSize;

            }

        }

 

        #endregion

 

        #region Data

 

        private int _operationBatchSize = 100;

        public int OperationBatchSize

        {

            get

            {

                return this._operationBatchSize;

            }

        }

 

        #endregion

 

        #endregion

 

        #region Public Constructors

 

        public Config1()

        {

        }

 

        public Config1(XmlDocument doc)

        {

            this._xmlDoc = doc;

            try

            {

                LoadValuesFromXmlDocument();

            }

            catch

            {

                // TODO: Handle Exception

            }

        }

 

        #endregion

 

        #region Public Methods

 

        public static Config1 Load()

        {

            Config1 configuration = new Config1();

 

            if (HttpRuntime.Cache.Get(_cacheKey) != null)

            {

                configuration = HttpRuntime.Cache.Get(_cacheKey) as Config1;

            }

            else

            {

                string path = HttpContext.Current.Server.MapPath("~/config/keyvan.config");

 

                XmlDocument doc = new XmlDocument();

                doc.Load(path);

 

                configuration = new Config1(doc);

 

                HttpRuntime.Cache.Insert(_cacheKey, configuration, new CacheDependency(path),

                    DateTime.MaxValue, new TimeSpan(12, 0, 0));

            }

 

            return configuration;

        }

 

        public XmlNodeList GetTasksNodes()

        {

            return GetConfigSections("Keyvan/Tasks/Task");

        }

 

        public XmlNode GetConfigSection(string nodePath)

        {

            return this._xmlDoc.SelectSingleNode(nodePath);

        }

 

        public XmlNodeList GetConfigSections(string nodesPath)

        {

            return this._xmlDoc.SelectNodes(nodesPath);

        }

 

        #endregion

 

        #region Private Methods

 

        private void LoadValuesFromXmlDocument()

        {

            // Site

            XmlNode node = GetConfigSection("Keyvan/Site");

            XmlAttributeCollection attributes = node.Attributes;

 

            XmlAttribute attribute = attributes["siteDomainName"];

            if (attribute != null)

                this._siteTitle = attribute.Value;

 

            attribute = attributes["siteTitle"];

            if (attribute != null)

                this._siteTitle = attribute.Value;

 

            attribute = attributes["siteDescription"];

            if (attribute != null)

                this._siteDescription = attribute.Value;

 

            attribute = attributes["sitePageSize"];

            if (attribute != null)

                this._sitePageSize = int.Parse(attribute.Value);

 

            // Data

            node = GetConfigSection("Keyvan/Data");

            attributes = node.Attributes;

 

            attribute = attributes["operationBatchSize"];

            if (attribute != null)

                this._operationBatchSize = int.Parse(attribute.Value);

        }

 

        #endregion

    }

}

Test

At this point, I’m able to test my configuration system. I can write a code that loads site title and description from this configuration system to set them for a page.

protected void Page_Load(object sender, EventArgs e)

{

    Config1 config = Config1.Load();

 

    this.Page.Title = config.SiteTitle;

 

    HtmlMeta description = new HtmlMeta();

    description.Attributes.Add("name", "description");

    description.Attributes.Add("content", config.SiteDescription);

    this.Page.Header.Controls.Add(description);

}

Note that I have created a new instance of Config1 class by calling the Load function.

In the next part I will rewrite this configuration system with XLinq. Let me publish the source code sample for this part along the second part.

[advertisement] Axosoft OnTime 2008 is four developer tools in one: bug tracking, project wiki, feature management, and help desk. It manages your development process so developers can focus on coding. Installed or Hosted – Free Single-user license -- Free 30-day team trial.

8 Comments : 08.20.08

Feedbacks

 avatar
#1
Dave Burke
08.20.2008 @ 1:48 PM

I shouldn't say this on a public channel, but I love you, man. Everything I want to learn about and implement in the .NET Open Source space...you blog!!!

Thanks for writing this up. You tweeted that you were ready to start a new series. Is this post part of one?

admin avatar
#2
Keyvan Nayyeri
08.20.2008 @ 1:51 PM

@Dave:

Thank you for all your kind words. You always have made me blush :-)

And yes, this is :-)

 avatar
#3
Dave Burke
08.20.2008 @ 3:59 PM

Part of a series??? WOOHOO!

btw, I'm happy your Mom and Sister are back from Saudi Arabia. (I enjoy following your twitter stream...)

admin avatar
#4
Keyvan Nayyeri
08.20.2008 @ 9:26 PM

@Dave:

Thank you so much, Dave :-)

 avatar
#5
Everyman Links for August 20, 2008
08.20.2008 @ 10:45 PM

Everyman Links for August 20, 2008

Pingback from Dew Drop - August 21, 2008 | Alvin Ashcraft's Morning Dew

In the first part I wrote about building an XML configuration system for the ASP.NET based on the XML

 avatar
#8
My Best Blog Posts in 2008
12.31.2008 @ 1:41 PM

In the past 3.5 years of blogging, I haven’t had such best pick up collections in the end of the year, but now that everybody is writing one, why shouldn’t I write my own?! Collecting this list, I could realize some interesting facts that completely changed

Leave a Comment