One of the issues I faced recently was to build a dynamic site map path for given page. At first this seems like a piece of cake, the MSDN contains an article regarding the SiteMapResolve event which is perfectly suited for this task.
Alas, this would be too simple. Do you see why the MSDN example is completely broken and will not work at all?
Well, the answer is here:
private void Page_Load(object sender, EventArgs e)
{
// The ExpandForumPaths method is called to handle
// the SiteMapResolve event.
SiteMap.SiteMapResolve +=
new SiteMapResolveEventHandler(this.ExpandForumPaths);
}
You see, the SiteMapResolve is a static event which means that every time the page is loaded it will add yet another handler for this event! After few thousands of requests, few thousands instances of the same handler will be attached to the the event! And guess what? Since all handlers return a value and the event expects a single value, the value from the handler which was attached as the first one will always be returned.
A complete disaster!
There's one way to overcome this mess - you have to attach an event and then unattach it. This approach is described in the CodeProject's A Better SiteMapResolve article.
However, I think that this approach has two major disadvantages:
- your page has to inherit from specific BasePage class
- even though handlers are attached and then unattached, threading issues still can occur since it is possible that for a period of time more than one handler is attached to the event
How to solve this issue then once and for all?
Well, let's have a single event handler, attached once in the Application.Start event. Then, when the handler fires, we'll check whether current handler (page) implements our specific interface and if this is so, we'll redirect the execution to the handler.
Let's start with the definition of the interface:
public interface ISiteMapResolver
{
SiteMapNode SiteMapResolve( object sender, SiteMapResolveEventArgs e );
}
Let's also modify the GlobalApplication class to add a single handler for the SiteMapResolve:
public class GlobalApplication : System.Web.HttpApplication
{
protected void Application_Start( object sender, EventArgs e )
{
/* SiteMap resolve */
SiteMap.SiteMapResolve += new SiteMapResolveEventHandler( Provider_SiteMapResolve );
}
SiteMapNode Provider_SiteMapResolve( object sender, SiteMapResolveEventArgs e )
{
if ( e.Context.CurrentHandler is ISiteMapResolver )
return ( (ISiteMapResolver)e.Context.CurrentHandler ).SiteMapResolve( sender, e );
else
return null;
}
...
and then let's implement the interface on a specific page:
public partial class TheSpecificPage : Page, ISiteMapResolver
{
...
public SiteMapNode SiteMapResolve( object sender, SiteMapResolveEventArgs e )
{
// build a dynamic, page specific site map path to show it
// in the SiteMapPath control
SiteMapNode parent = new SiteMapNode( e.Provider, "1", "~/TheStartPage.aspx", "Start page" );
SiteMapNode child = new SiteMapNode( e.Provider, "2", "~/TheSpecificPage.aspx", "Specific page" );
child.ParentNode = parent;
return child;
}
}