I'm Keyvan Nayyeri, a 25 years old Ph.D. student at
the Computer Science department of
the University of Texas at San Antonio.
I'm also
a Software Architect and Developer and previously held a B.Sc.
degree in Applied Mathematics.
This is my blog where I publish content about various topics specifically Programming Languages and Compilers, Software
Engineering and Programming.
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.
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 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
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>();
}
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
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
}
}
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);
}
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.
You can download the source code sample for both parts from here.
Reflective Perspective - Chris Alcock » The Morning Brew #168
Aug 29, 2008 2:19 AM
#
Pingback from Reflective Perspective - Chris Alcock » The Morning Brew #168
Dew Drop - August 29, 2008 | Alvin Ashcraft's Morning Dew
Aug 29, 2008 8:03 AM
#
Pingback from Dew Drop - August 29, 2008 | Alvin Ashcraft's Morning Dew
Amin
Aug 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 :))
Fabio
Sep 05, 2008 8:50 AM
#
Hi Keyvan,
Any reason why you chose not to implement ConfigurationSection and ConfigurationElement to define your configuration class?
Fabio
Keyvan Nayyeri
Sep 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.
My Best Blog Posts in 2008
Dec 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