XmlSiteMapProvider – Query String Support
Most of the ASP.NET applications that I create have some type of a menu and the menu is typically dynamic. When I say dynamic I mean that the menu items are usually dependent on the user's current location in the application and sometimes require query strings (i.e. the Id of a photo album). The default XmlSiteMapProvider is nice and can be very useful coupled with the securityTrimmingEnabled attribute (the provider model is very awesome...thank you Rob Howard). However, the default XmlSiteMapProvider does not offer any support for query strings. I wanted a solution that would allow me to use the existing .sitemap format, automatically adding query strings to menu items, hide menu items if those query strings were not available, and did not require creating any custom controls. I first looked at the SiteMapProvider.SiteMapResolveEventHandler event, but this option would only work if I was using the SiteMapPath control. I then decided to see what was overridable in XmlSiteMapProvider since that provider did exactly what I wanted with the exception of handling query strings. I came up with the class below that handles all of my requirements and simply added the new provider to the SiteMap providers in the web.config. You can download a small sample application here.
using System;
using System.Web;
using System.Collections.Specialized;
public class XmlQueryStringSiteMapProvider : XmlSiteMapProvider
{
/// Gets the root node of the site map.
public override SiteMapNode RootNode
{
get
{
// Get the root node
SiteMapNode root = base.RootNode;
// Make sure we have a valid root node object
if (root == null)
return null;
// Clone the root node
SiteMapNode tempNode = root.Clone();
// Check to see if this node requires a query string
string qs = GetQueryString(tempNode);
if (!string.IsNullOrEmpty(qs))
tempNode.Url += qs;
return tempNode;
}
}
/// Retrieves the child site map nodes of a specific SiteMapNode object.
public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
{
// Make sure we have a valid node
if (node == null)
throw new ArgumentNullException("node");
// Build the site map
this.BuildSiteMap();
// Get a collection of all child nodes
SiteMapNodeCollection collection = base.GetChildNodes(node);
// Make sure we have a vlid collection of child nodes
if (collection == null)
return new SiteMapNodeCollection();
// Create a new collectino that will hold the final nodes
SiteMapNodeCollection nodes = new SiteMapNodeCollection(collection.Count);
foreach (SiteMapNode n in collection)
{
// Clone the current node
SiteMapNode tempNode = n.Clone();
// Make sure this node is accessible
if (base.SecurityTrimmingEnabled && !n.IsAccessibleToUser(HttpContext.Current))
continue;
// Check to see if this node requires a query string
string qs = GetQueryString(tempNode);
if (!string.IsNullOrEmpty(qs))
tempNode.Url += qs;
else if (n["required"] == "true" && !string.IsNullOrEmpty(n["queryString"]))
continue; // DO not add this node to the collection
nodes.Add(tempNode);
}
return SiteMapNodeCollection.ReadOnly(nodes);
}
/// Creates a query string based on the node settings.
private string GetQueryString(SiteMapNode node)
{
// Check to see if this node requires query string values
if (node["queryString"] == null)
return null;
// Get a list of the query string names
NameValueCollection values = new NameValueCollection();
string[] vars = node["queryString"].Split(",".ToCharArray());
// Check to see if we have values for the query string names
foreach (string s in vars)
{
string var = s.Trim();
// Check to see if the query string value exists
if (HttpContext.Current.Request.QueryString[var] == null)
continue;
// Add the query string value to the collection
values.Add(var, HttpContext.Current.Request.QueryString[var]);
}
if (values.Count == 0)
return null;
// Build the query string
string[] parts = new string[values.Count];
for (int i = 0; i < values.AllKeys.Length; i++)
parts[i] = values.AllKeys[i] + "=" + values[values.AllKeys[i]];
return "?" + String.Join("&", parts);
}
}
New Blog Application
I finished the basic version of my new blog system this evening with the addition of the comment system. My blog application is not as feature rich as .Text at the moment, but it has been a fun project. Over the next week, I am going to update the Articles and Examples sections with new content.
The weekend is at hand! We watched both of The Incredibles DVDs last weekend. I always enjoy watching the "behind the scenes" and seeing what the guys at Pixar go through making their great movies. Their movies would not be possible without their awesome software developers (their artists and animators are ok too
!

