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

In the first part I wrote about building an XML configuration system for the ASP.NET based on the XML manipulation API offered in .NET 1.x, 2.0 and 3.0 and promised to rewrite this system in the second part with new XLinq API introduced in .NET 3.5. So here is that post!

The fundamental of the configuration system is same as the past: I define appropriate properties and fields and load the data into memory then cache them to improve the performance. The only difference is in the way that XML configuration file is parsed.

I rewrite the system by replacing some classes like XmlDocument, XmlNodeList, XmlNode, XmlAttributeCollection and XmlAttribute with new classes such as XDocument, XElement and XAttribute.

Reminder

In the first post I defined an XML configuration file that is constant for this part as well.

<?xml version="1.0"?>

<Keyvan>

  <Site

    domainName="Site.Com"

    siteTitle="Site Title"

    siteDescription="A description for your site."

    sitePageSize="10"

    />

  <Data operationBatchSize="200" />

  <Tasks>

    <Task name="Emailing"

          type="XmlConfigurationSample.Emailing, XmlConfigurationSample"

          interval="600000"

          enabled="true"

          priority="1" />

  </Tasks>

</Keyvan>

I also defined some fields and properties that are almost constant except that I replace my XmlDocument with an XElement field that is the points to the root <Keyvan /> element of my configuration file.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Xml;

using System.Xml.Linq;

 

namespace XmlConfigurationSample.Code

{

    public class Config2

    {

        #region Fields

 

        private XElement _rootNode = null;

        private const string _cacheKey = "KeyvanConfiguration2";

 

        #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

Public constructor for Config2 is not very different from constructor for Config1 except that it gets an XDocument object as its parameter. It also sets the root node of the configuration file for the ease of development.

#region Public Constructors

 

public Config2()

{

}

 

public Config2(XDocument doc)

{

    this._rootNode = doc.Element("Keyvan");

    try

    {

        LoadValuesFromXmlDocument();

    }

    catch

    {

        // TODO: Handle Exception

    }

}

 

#endregion

Public Methods

Here there is a difference between the first implementation and this new implementation. My first implementation offered some helper public methods to get access to specific sections of the configuration file via XPath queries but this new implementation doesn’t offer such a functionality because the fundamental structure of XLinq is not very straightforward for XPath queries.

My new Load method has a similar structure as my first implementation but it loads an XDocument from the configuration file to initialize the configuration class. Like the past, caching has an important effect on the performance.

public static Config2 Load()

{

    var configuration = new Config2();

 

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

    {

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

    }

    else

    {

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

 

        var doc = XDocument.Load(path);

 

        configuration = new Config2(doc);

 

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

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

    }

 

    return configuration;

}

I also want to rewrite the GetTasksNodes function. Previously it was returning an XmlNodeList object of all the <Task /> nodes defined in the configuration file but now it returns a list of XElement objects for each <Task /> element. This is more convenient for a developer when he’s dealing with task nodes.

public List<XElement> GetTasksNodes()

{

    return this._rootNode.Element("Tasks").Elements("Task").ToList<XElement>();

}

Private Methods

There was also a single private method called LoadValuesFromXmlDocument that played the main role in the implementation. This method, with a new implementation, is still an integral part of my implementation.

The new implementation is heavily dependent on XLinq API and extracts all the configuration data from the XML via this object model.

#region Private Methods

 

private void LoadValuesFromXmlDocument()

{

    // Site           

    var node = this._rootNode.Element("Site");

    XAttribute attribute = null;

 

    if (node.HasAttributes)

    {

        attribute = node.Attribute("domainName");

        if (attribute != null)

            this._siteDomainName = attribute.Value;

 

        attribute = node.Attribute("siteTitle");

        if (attribute != null)

            this._siteTitle = attribute.Value;

 

        attribute = node.Attribute("siteDescription");

        if (attribute != null)

            this._siteDescription = attribute.Value;

 

        attribute = node.Attribute("sitePageSize");

        if (attribute != null)

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

    }

 

    // Data

    node = this._rootNode.Element("Data");

    if (node.HasAttributes)

    {

        attribute = node.Attribute("operationBatchSize");

        if (attribute != null)

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

    }

}

 

#endregion

Wrapping Up

I can wrap my implementation up in a simple class that follows:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Xml;

using System.Web.Caching;

using System.Xml.Linq;

 

namespace XmlConfigurationSample.Code

{

    public class Config2

    {

        #region Fields

 

        private XElement _rootNode = null;

        private const string _cacheKey = "KeyvanConfiguration2";

 

        #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 Config2()

        {

        }

 

        public Config2(XDocument doc)

        {

            this._rootNode = doc.Element("Keyvan");

            try

            {

                LoadValuesFromXmlDocument();

            }

            catch

            {

                // TODO: Handle Exception

            }

        }

 

        #endregion

 

        #region Public Methods

 

        public static Config2 Load()

        {

            var configuration = new Config2();

 

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

            {

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

            }

            else

            {

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

 

                var doc = XDocument.Load(path);

 

                configuration = new Config2(doc);

 

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

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

            }

 

            return configuration;

        }

 

        public List<XElement> GetTasksNodes()

        {

            return this._rootNode.Element("Tasks").Elements("Task").ToList<XElement>();

        }

 

        #endregion

 

        #region Private Methods

 

        private void LoadValuesFromXmlDocument()

        {

            // Site           

            var node = this._rootNode.Element("Site");

            XAttribute attribute = null;

 

            if (node.HasAttributes)

            {

                attribute = node.Attribute("domainName");

                if (attribute != null)

                    this._siteDomainName = attribute.Value;

 

                attribute = node.Attribute("siteTitle");

                if (attribute != null)

                    this._siteTitle = attribute.Value;

 

                attribute = node.Attribute("siteDescription");

                if (attribute != null)

                    this._siteDescription = attribute.Value;

 

                attribute = node.Attribute("sitePageSize");

                if (attribute != null)

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

            }

 

            // Data

            node = this._rootNode.Element("Data");

            if (node.HasAttributes)

            {

                attribute = node.Attribute("operationBatchSize");

                if (attribute != null)

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

            }

        }

 

        #endregion

    }

}

Test

In order to test this new implementation, I just replace my last test code with a new code that applies Config2 as its configuration source.

protected void Page_Load(object sender, EventArgs e)

{

    //Config1 config = Config1.Load();

    Config2 config = Config2.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);

}

Conclusion

I don’t have any examination on the performance difference between these two approaches and I don’t think there is any major difference. The main point is in the ease of dealing with XLinq classes rather than traditional System.Xml classes. I personally prefer this new implementation and have had better experiences with it. By the way, both approaches are discussed here and you can adapt them for your own.

Download

You can download the source code sample for both parts from here.

[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.

6 Comments : 08.28.08

Feedbacks

Pingback from Reflective Perspective - Chris Alcock » The Morning Brew #168

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

 avatar
#3
Amin
08.29.2008 @ 11:50 PM

hi

I'm from Iran.

I'm developer with C#, vb.net, asp.net

I'm happy to visit your site and read your post. CONGRATULATIONS

I found your site today.

I don't know you understand persian or not?

I hope you will SUCCESS.

(i cann't write english very well :))

 avatar
#4
Fabio
09.05.2008 @ 8:50 AM

Hi Keyvan,

Any reason why you chose not to implement ConfigurationSection and ConfigurationElement to define your configuration class?

Fabio

admin avatar
#5
Keyvan Nayyeri
09.05.2008 @ 8:54 AM

@Fabio:

The reasons are somehow predictable:

1- The complexity that it can add to your configuration file which can make it hard to organize and manage the configuration.

2- Independency between my configuration system and .NET configuration system that lets me manage my configuration and have control over it.

 avatar
#6
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