Greg Beech's Website

Making SiteMapPath work with QueryStrings

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
Filed under:

Comments

Anthony wrote re: Making SiteMapPath work with QueryStrings
on 03-05-2008 9:09 AM

Hello Greg,

Thanks a lot man, you solved a though problem indeed. I pasted your code and it worked instantaniously, without any adjustments. I cannot understand why the sitemappath doesn't has this capability out of the box. I mean, how many dynamic websites are out there that don't use querystrings?

Greetings from Belgium,

Anthony

Juraj wrote re: Making SiteMapPath work with QueryStrings
on 03-14-2008 1:00 PM

Awesome Awesome, thanks, saved me a lot of time and pain and effort. nice one. :)

Juraj, South Africa

Arun George wrote re: Making SiteMapPath work with QueryStrings
on 03-20-2008 4:02 PM

Greg,

Thanks a million for this lucid post. I was amazed why SiteMapPath, even though being a dev friendly 2.0 control, lacks the basic need to support QueryStrings.

Thanks Again.

Arun, USA

Divya wrote re: Making SiteMapPath work with QueryStrings
on 04-08-2008 9:02 PM

That worked perfectly! Thanks!!

Stig wrote re: Making SiteMapPath work with QueryStrings
on 04-14-2008 5:50 AM

Great! Thanks for sharing this code!

Zipfeli wrote re: Making SiteMapPath work with QueryStrings
on 04-24-2008 6:52 AM

Thank you so much, works like a charm!

Raphu wrote re: Making SiteMapPath work with QueryStrings
on 05-26-2008 1:36 PM

Thanks alot. I have been looking for this for sometime since most of my pages are dynamic. I am really happy!

anjum wrote re: Making SiteMapPath work with QueryStrings
on 06-03-2008 5:57 PM

worked like a charm... thanks Greg

Dilip Nikam wrote re: Making SiteMapPath work with QueryStrings
on 07-18-2008 6:15 AM

We can do this way also. In this example I am showing how to add functionality of show-hide SubMenu, Enable-Disable User Click and AddQueryString.

<siteMapNode url="State/State.aspx#" IsClickAble="False" title="States" description="States" >    

     <siteMapNode url="State/State.aspx" AddQueryString="ID={0}" title="Create New State"  Visible="True" />

     <siteMapNode url="State/AllState.aspx"  title="All States"  Visible="True" />

     <siteMapNode url="State/StateMapping.aspx" title="State Field Mapping"  Visible="True" />

     <siteMapNode url="State/DeactivetedState.aspx" title="Deactiveted State"  Visible="False"/>

</siteMapNode>

protected void TopNavigationMenu_OnMenuItemDataBound(object sender, MenuEventArgs e)

   {

       SiteMapNode node = e.Item.DataItem as SiteMapNode;

       // check for the visible attribute and if false

       // remove the node from the parent

       // this allows nodes to appear in the SiteMapPath but not show on the menu

       if (!string.IsNullOrEmpty(node["Visible"]))

       {

           bool isVisible;

           if (bool.TryParse(node["Visible"], out isVisible))

           {

               if (!isVisible)

               {

                   e.Item.Parent.ChildItems.Remove(e.Item);

               }

           }

       }

       //Block User Click.

       if (!string.IsNullOrEmpty(node["IsClickAble"]))

       {

           e.Item.Selectable = false;

       }

       //Adding QueryString value In RunTime.

       if (!string.IsNullOrEmpty(node["AddQueryString"]))

       {

           e.Item.NavigateUrl = e.Item.NavigateUrl + "?" + node["AddQueryString"].ToString();

           string s = e.Item.NavigateUrl.ToString();

       }

   }

thanks Greg.....

Breadcrumb / Footprint Example - .Net Development wrote Breadcrumb / Footprint Example - .Net Development
on 09-24-2008 4:41 PM

Pingback from  Breadcrumb / Footprint Example - .Net Development

Add a Comment

(required)  
(optional)
(required)  
Remember Me?

Enter the numbers above:
Copyright (C) Greg Beech. All rights reserved.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems