SiteMap and URL Rewriting in ASP.NET 2.0
Introduction
A common scenario in web development is when one page shows different content depending on some query parameters. OK, but these query parameters do not look friendly to the user and are neither friendly for the search engines. Here the URL Rewriting comes. We have URL rewriting in SilverlightShow.net sitemap page. Look for example here:
http://www.silverlightshow.net/Sitemap-Articles.aspx
You see that the articles are organized in pages and when clicking on “Older Articles” or “Newer Articles” the page is changed and the URL looks slightly different. Only the number at the end of the page name is different, for example “Sitemap-Articles-1.aspx” or “Sitemap-Articles-2.aspx”.
The problem
It’s needless to say that all these different URLs are processed by a single ASP.NET page. The problem came when we decided to put an ASP.NET SiteMapPath control. By default it automatically binds using the information provided in the Web.sitemap file. OK, we have the following SiteMap structure:
<?xmlversion="1.0"encoding="utf-8" ?>
<siteMapxmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNodeurl="~/Default.aspx"title="Home" description="Home">
<siteMapNodeurl="~/Sitemap.aspx"title="Sitemap"description="Sitemap">
<siteMapNodeurl="~/Sitemap-News.aspx"title="News"description="News"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Articles.aspx"title="Articles" description="Articles">
<siteMapNodeurl="~/Sitemap-Announcements.aspx"title="Announcements"description="Announcements"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Products.aspx"title="Products"description="Products">
<siteMapNodeurl="~/Sitemap-Controls.aspx"title="Controls"description="Controls"></siteMapNode>
<siteMapNodeurl="~/Sitemap-MediaPlayers.aspx"title="Media Players"description="Media Players"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Games.aspx"title="Games"description="Games"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Charts.aspx"title="Charts"description="Charts"></siteMapNode>
</siteMapNode>
<siteMapNodeurl="~/Sitemap-Tools.aspx"title="Tools"description="Tools"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Moonlight.aspx"title="Moonlight"description="Moonlight"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Demos.aspx"title="Demos"description="Demos"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Learn.aspx"title="Learn"description="Learn">
<siteMapNodeurl="~/Sitemap-Videos.aspx"title="Videos"description="Videos"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Presentations.aspx"title="Presentations"description="Presentations"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Tutorials.aspx"title="Tutorials"description="Tutorials"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Resources.aspx"title="Resources"description="Resources"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Samples.aspx"title="Samples"description="Samples"></siteMapNode>
<siteMapNodeurl="~/Sitemap-QuickStarts.aspx"title="QuickStarts"description="QuickStarts"></siteMapNode>
<siteMapNodeurl="~/Sitemap-TipsandTricks.aspx"title="Tips and Tricks"description="Tips and Tricks"></siteMapNode>
</siteMapNode>
<siteMapNodeurl="~/Sitemap-Issues.aspx"title="Issues"description="Issues"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Misc.aspx"title="Misc"description="Misc"></siteMapNode>
</siteMapNode>
<siteMapNodeurl="~/Sitemap-Shows.aspx"title="Shows"description="Shows"></siteMapNode>
</siteMapNode>
</siteMapNode>
</siteMap>
We have the root website node, marked with title “Home” and the sitemap node marked with title “Sitemap”. All child nodes of the “Sitemap” node except the last child (Shows) are processed by a single ASP.NET page. Since the URL is different no matter that all these nodes are in fact one ASP.NET page the SiteMapPath works the way it has to work. For example if we are on the following URL:
http://www.silverlightshow.net/Sitemap-Articles.aspx
Our SiteMapPath is rendered the following way:

If you look at our Web.sitemap file you will see that it works fine. Till now everything is OK, but imagine that we want to open the last page in the Articles page, which is on the following URL:
http://www.silverlightshow.net/Sitemap-Articles-1.aspx
It works on SilverlightShow.net because we have made it work but in common scenario it wouldn’t work. No SiteMapPath would be shown because in the Web.sitemap we don’t have a node with url="~/Sitemap-Articles-1.aspx". You can always say that we can add this node too but in our case lots of articles are added to the site every day and the pages become more and more. I don’t like the option of checking for new pages every day and updating the Web.sitemap file.
The solution
The solution is to attach to the SiteMap.SiteMapResolve in the OnLoad handler of the page that processes the request where paging is needed.
We have the following code in the code behind of our page:
protected override void OnLoad( EventArgs e )
{
base.OnLoad( e );
SiteMap.SiteMapResolve += new SiteMapResolveEventHandler( this.ConstructSiteMap );
Note: You can do the same in the Page_Load method if your ASP.NET page is marked with: AutoEventWireup="true". I’m overriding the OnLoad event handler because it is faster :)
Note: SiteMap class is located in the System.Web namespace and is an in-memory representation of the navigation structure of the website. It is used by the SiteMapPath control.
Here is the handler for the SiteMapResolve event.
private SiteMapNode ConstructSiteMap( Object sender, SiteMapResolveEventArgs e )
{
SiteMapNode currentNode;
if ( SiteMap.CurrentNode == null )
{
currentNode = e.Provider.FindSiteMapNode( "~/Sitemap-Articles.aspx " );
}
else
{
currentNode = SiteMap.CurrentNode.Clone( true );
}
return currentNode;
}
In our case if the URL is with paging it is not found in the Web.sitemap file and that’s why the SiteMap.CurrentNode property will be null. This causes the SiteMapPath control to disappear. OK, now we check if this property of the SiteMap is null and if it is we just create a SiteMapNode using the current provider’s SiteMapProvider.FindSiteMapNode method giving the URL without paging. Note that we surely will know the URL that must be given to the FindSiteMapNode method because we are in the page which processes the request. If this property is not null we just clone it. The return value of our handler will be the SiteMapNode which will be used to render the SiteMapPath control.
This is a better solution than updating the Web.sitemap file every day.
Additional Issues and solutions
I found out that the SiteMap class has a strange way of caching. For example if we go to: http://www.silverlightshow.net/Sitemap-Controls.aspx our SiteMapPath will look like this:

After that we are going to http://www.silverlightshow.net/Sitemap-Articles.aspx and our SiteMapPath is normal:

But the problem comes when we go through the pages. If we hadn’t find a solution our SiteMapPath path would look this way, no matter that we are on the Articles page and not on the Controls page:

This behavior is as strange as our solution. It seems that the SiteMap remembered that when last time the CurrentNode was null we made it “~/Sitemap-Controls.aspx” and when we are on Articles page and this node is again null it uses the previous value from Controls. As I told you our solution is also strange but works :) We are putting our page path in variable instead of literal:
currentNode = e.Provider.FindSiteMapNode( this.currentPagePath );
Our variable of type string currentPagePath contains for example the following value “~/Sitemap-Games.aspx” for the following page: http://www.silverlightshow.net/Sitemap-Games.aspx. The behavior is exactly the same. But if we make this variable null just after our page is unloaded no caching takes place:
protected override void OnUnload( EventArgs e )
{
base.OnUnload( e );
this.currentPagePath = null;
}
Note: In our case our Controls and Articles pages are processed by a single ASP.NET page. But the caching behavior of the SiteMap takes place even these two URLs are processed by different ASP.NET pages. Our solution works for our Shows page which is different ASP.NET page. You just must make the variable which contains the current node path null on both places.