There are many ways to load and parse a custom XML document, but we want to use the very newest and easiest way in the .NET framework--the XElement object in System.Xml.Linq. By doing so, we should be able to remove lots of old code and eliminate the possibility of bugs and typos.
Let me explain what I know about XElement and XDocument. The .NET framework introduced these objects recently, and what they do is make using XML extremely easy. To load an XML document into memory--any XML document--you only need one line of code. Then you can use methods on the XElement object to walk or explore the various parts of the document.
First, I want to show you a sample of what the XML document I am using in this example looks like, and where it is located. For my example site, I am showing XML that lists a bunch of items in a sitemap. Sitemaps are ideal for XML, and the following XML is an example of what mine looks like.
<?xml version="1.0"?>
<ArrayOfSitePage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SitePage>
<Visibility>Supplementary</Visibility>
<Title>C# .NET Examples and Resources</Title>
<Url></Url>
<Category>None</Category>
</SitePage>
<SitePage>
<Visibility>Supplementary</Visibility>
<Title>Usability Guidelines for Web Writing</Title>
<Url>Content/Usability-Writing.aspx</Url>
<Category>None</Category>
</SitePage>
<SitePage>
<Visibility>Regular</Visibility>
<Title>Alphanumeric Sorting in C#</Title>
<Url>Content/Alphanumeric-Sorting.aspx</Url>
<Category>Algorithms</Category>
</SitePage>
<SitePage>
<Visibility>Regular</Visibility>
<Title>Word Count Algorithm in C#</Title>
<Url>Content/Word-Count-Algorithm.aspx</Url>
<Category>Algorithms</Category>
</SitePage>
</ArrayOfSitePage>
Sorry about that--don't spend time looking at it too carefully. What you should notice is that it has some tags and a simple structure. Let me show a table that elaborates on each element. This will give you some ideas about how the system works.
| Custom XML element | Its usage |
| ArrayOfSitePage | Root element for the XML, no real meaning |
| SitePage | Single XML element, a discrete page object |
|
Visibility, Title, Url |
Custom tags in the XML that describe the SitePage |
For my example, I placed the above XML in the App_Data folder in my Website project in Visual Studio 2008. As a reminder, App_Data is a special ASP.NET folder that you should use to store database files or XML files that are the site's data. It is useful to store files there because is provides some uniformity and structure to your project.
You could have an XML file in your App_Data folder called Map1.xml. Let's look at the App_Code folder and then add a new C# class. The next file I show is a C# class that stores an XElement. Skim over the directives and the singleton code, and look at the _x object.
using System.IO;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Xml.Linq;
/// <summary>
/// This is a singleton object that stores an XElement object, which is an in-memory
/// cache of an XML file.
/// </summary>
public sealed class SiteStructure
{
static readonly SiteStructure _instance = new SiteStructure();
public static SiteStructure Instance
{
get { return _instance; }
}
/// <summary>
/// Stores our XML map document.
/// </summary>
XElement _x;
// [omitted] etc.
}
XElement can be used to store an entire XML document in memory, and it is extremely simple to use. Let's look at the private constructor for the above class, which is run only once. It sets up the XElement and loads it into memory. After that, we will be able to run queries on it very quickly.
/// <summary>
/// Load the Map1.xml file that is in App_Data directly into the in-memory
/// object XElement _x. After this is done, you can run LINQ to XML queries
/// on the XElement quickly.
/// </summary>
private SiteStructure()
{
string mapLoc = HttpContext.Current.Request.MapPath("~/App_Data/Map1.xml");
_x = XElement.Load(mapLoc);
}
This next section assumes some knowledge about LINQ. I will suggest some resources about LINQ afterwards, but for now let's take a peek at the code that queries the above XElement and actually uses that XML file I pasted near the start of this article. We are using the XML like a database, but it is entirely in-memory and the queries are native.
/// <summary>
/// Use LINQ to XML to query the in-memory XElement tree. We count the number of pages
/// that have the same Category value in their XML markup as the current page, which is
/// known by its title.
/// </summary>
public int CountCategory(string titleIn, out string categoryName,
out stringcategoryId)
{
try
{
// Use var keyword syntax with LINQ.
var thisPage = from page in _x.Elements("SitePage")
where titleIn == page.Element("Title").Value
select page;
string cat = thisPage.First().Element("Category").Value;
categoryName = _displayDict[cat];
categoryId = cat;
return (from p in _x.Elements("SitePage")
where p.Element("Category").Value == cat &&
p.Element("Visibility").Value == "Regular"
select p).Count(); // Uses Count extension method
}
catch
{
categoryId = "";
categoryName = "None";
return 0;
}
}
The line statements and logic in the above function are custom, but I show it to you so you can see an example. What this function does is find the category of the page, then count all pages in that category, and then set some out parameters and returns the count number. This can be used to build landing pages or pathway pages that contain richer data and relationships with the rest of the site.
In my mind there is no better alternative than loading an XML file in a single statement, and then running simple queries on it to generate output for the site. It is hard to get any simpler using today's technologies, such as XML and LINQ. There are many things you can do to make the technique better, such as storing special values in XML tags, even in the same files.