When building this site I came across the issue that the ASP.NET 2.0 SiteMapPath control doesn't record querystring data by default. There are a number of solutions posted on the web doing such things as registering page dependencies on certain parameters in the Web.sitemap file or writing specific handlers in each page, but none of them are trivial and all rely on the fact that all query string parameters are preserved to child pages, which in the photos section of this site they aren't.
It seemed to me that the only way to record the path through the site is to store state between requests about the navigation path to the current page. The basic solution is to hook into the SiteMapResolve event of SiteMap and append the querystring to the node, then store the node in session state. On the next request we check whether the parent node of the current page is the same as the previous page, and therefore we can assume that the child was navigated to from the parent. As the nodes are hierarchical, storing the previous node with the querystring also stores the complete hierarchy of nodes with appended querystrings.
The following code needs to be placed in the global application file, Global.asax:
void Application_Start(object sender, EventArgs e)
{
SiteMap.SiteMapResolve += new SiteMapResolveEventHandler(OnSiteMapResolve);
}
static SiteMapNode OnSiteMapResolve(object sender, SiteMapResolveEventArgs e)
{
//if we aren't working on a node then return
if (SiteMap.CurrentNode == null)
{
return null;
}
//clone the current node and append the query string
SiteMapNode resolvedNode = SiteMap.CurrentNode.Clone(false);
resolvedNode.Url += e.Context.Request.Url.Query;
if (resolvedNode.ParentNode != null)
{
//get the previous node and if it has the same key (basic url) as the current
//node's parent then assign it as the parent
SiteMapNode previousNode = (SiteMapNode)e.Context.Session["SiteMapPreviousNode"];
if (previousNode != null && string.Compare(resolvedNode.ParentNode.Key, previousNode.Key, true) == 0)
{
resolvedNode.ParentNode = previousNode;
}
}
//store the resolved node in the session state and return it
e.Context.Session["SiteMapPreviousNode"] = resolvedNode;
return resolvedNode;
}
This appeared to work fantastically, until I started using the 'Back' button to navigate as well. In this case the current page is not a child of the previous page (as the server is not hit when the client navigates backwards) but it still is a child of one of the pages in the hierarchy. To handle this case, instead of just checking whether the previous node is the parent we need to traverse up the hierarchy and see whether any page in it is the parent, as shown in the modified code below.
void Application_Start(object sender, EventArgs e)
{
SiteMap.SiteMapResolve += new SiteMapResolveEventHandler(OnSiteMapResolve);
}
static SiteMapNode OnSiteMapResolve(object sender, SiteMapResolveEventArgs e)
{
//if we aren't working on a node then return
if (SiteMap.CurrentNode == null)
{
return null;
}
//clone the current node and append the query string
SiteMapNode resolvedNode = SiteMap.CurrentNode.Clone(false);
resolvedNode.Url += e.Context.Request.Url.Query;
if (resolvedNode.ParentNode != null)
{
//get the previous node and if it has the same key (basic url) as the current
//node's parent then assign it as the parent - note that we need to traverse up the hierarchy in
//case the user used the 'back' button rather than the breadcrumb trail to navigate
SiteMapNode previousNode = (SiteMapNode)e.Context.Session["SiteMapPreviousNode"];
while (previousNode != null)
{
if (string.Compare(resolvedNode.ParentNode.Key, previousNode.Key, true) == 0)
{
resolvedNode.ParentNode = previousNode;
break;
}
previousNode = previousNode.ParentNode;
}
}
//store the resolved node in the session state and return it
e.Context.Session["SiteMapPreviousNode"] = resolvedNode;
return resolvedNode;
}
This code is used for the breadcrumb trail on this site and I haven't found any major problems. If your site has a more complex navigational structure then this code may not be completely suitable but for many small sites it should just work. The main limitation as it stands is that the SiteMapNode class is not serializable and therefore this will only work when using in-process session state; that said it wouldn't be too hard to convert the hierarchy into a dictionary of URLs and QueryStrings which could be stored out of process and reconstruct the hierarchy from that.
Posted
Dec 26 2005, 09:36 PM
by
Greg Beech