I’ve updated my ASP.NET WebForm Routing demo with basic SiteMap support.  It’s targeting ASP.NET 3.5 SP1 and expects your WebForm (Page) to implement the IRoutablePage interface (or you can derive from RoutablePage).  If you’re using ASP.NET 4.0 you can omit this dependency (as RequestContext is already available on the Page🙂 ).

SiteMap raises the SiteMapResolve event whenever its CurrentNode property is read.  This gives you an opportunity to determine the current node based on the route, without requiring a custom SiteMapProvider.  The example below takes the current route URL, removes any route values and looks for a matching SiteMapNode:

private SiteMapNode SiteMap_SiteMapResolve( object sender, SiteMapResolveEventArgs e )
{
    var routable = e.Context.CurrentHandler as IRoutablePage;

    if ( routable != null )
    {
        var rc = routable.Routing.RequestContext;
        var route = rc.RouteData.Route;
        var segments = route.GetVirtualPath( rc, null ).VirtualPath.Split( '/' );
        var path = "~/" + string.Join( "/", segments.Take( segments.Length - rc.RouteData.Values.Count ).ToArray() );

        return SiteMap.Provider.FindSiteMapNodeFromKey( path );
    }

    return null;
}

You create a .sitemap file in the usual way, but put your “routed” URLs in instead:

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
    <siteMapNode url="~/" title="Home" description="Home">
        <siteMapNode url="~/Search" title="Search" description="Search" />
        <siteMapNode url="~/Details" title="Details" description="Details" />
        <siteMapNode url="~/NoMaster" title="No Master" description="No Master">
            <siteMapNode url="~/NoMaster/MySub" title="My Subpage" description="My Subpage"/>
        </siteMapNode>
    </siteMapNode>
</siteMap>

You can use the regular SiteMapPath control to show the current location.  However, you might prefer to render your own.  The sample project includes a couple of helper methods to achieve just that:

/// <summary>
/// Gets SiteMap path for the current request.
/// </summary>
/// <returns></returns>
public string SiteMapPath()
{
    var pages = SiteMap.CurrentNode.For( n => n != null, n => n.ParentNode ).Reverse().Select( n => SiteMapLink( n ) );

    return string.Join( " > ", pages.ToArray() );
}

/// <summary>
/// Gets a SiteMap link.
/// </summary>
/// <param name="node">The node.</param>
/// <returns></returns>
private string SiteMapLink( SiteMapNode node )
{
    var span = string.Format( "<span class=\"siteMapLink\">{0}</span>", node.Title );

    return ( node != SiteMap.CurrentNode )
        ? string.Format( "<a href=\"{0}\">{1}</a>", node.Url, span )
        : span;
}

You can read more about the “For” extension method in my earlier post.

Source code for the sample project is available on CodePlex.  You can try the demo online right here🙂 .