Practical ASP.NET
Managing Menus with a Custom Menu Provider
Creating your own module for managing menus is not only ridiculously easy, but opens the door for adding any other enhancements that you can think of.
In my last column (
Managing Menus), I answered a question posed by a participant in an ASP.NET class that I was teaching: How to have an item in the Web.sitemap not appear in a menu but still appear in the SiteMapPath control. For that solution, I controlled which MenuItems were added to the Menu based on a custom attribute that I added to the SiteMapNodes in the Web.sitemap file.
The problem with that solution is that the code to manage the menu must be put on every page that has a menu control. If your menus only appear on a Master Page and your site has only a single Master Page, that's not a burden. However, a better solution is to centralize your code in your own custom SiteMapProvider. It's not only a better solution, it's a cooler solution and ridiculously easy to implement.
Quick review: ASP.NET uses a "provider-based" architecture where significant functionality is delivered through snap-in/snap-out providers. These providers can be configured or even replaced through entries in the site's web.config file. I touched on sitemap providers briefly way back in 2006 in a column on customizing security. In that article, I pointed out a bug in the menu controls that was revealed when you turn on cookieless security or session management and that could be handled with a custom sitemap provider.
A sitemap provider is a class that inherits from XmlSiteMapProvider. Unlike some other provider classes (the Membership provider that's used for security) this class has no required methods -- you can pick and choose which methods on the base class you want to override.
The obvious property to override is the CurrentNode property which is called by whatever is using the provider (presumably, some kind of menu) whenever a MenuItem is required. To implement a solution that uses a SiteMapProvider, first add a Class file to your site's App_Code folder and have it inherit from XMLSitemapProvider:
Public Class PHVSiteMap
Inherits XmlSiteMapProvider
End Class
With the class in place, for this project you then need to override the GetChildNodes method which returns the list of children for any menu item (this is also used to get the main menu items which are children of the root menu). My first step is to let the base class do all the heavy lifting by calling the base class' version of the GetChildNodes method. Now that I've retrieved the nodes that should be present, I create a new collection and transfer to it only those nodes that meet my criteria. When I'm done, I return my collection from the method:
Public Overrides Function GetChildNodes( _
ByVal node As System.Web.SiteMapNode) As _
System.Web.SiteMapNodeCollection
Dim smnc As SiteMapNodeCollection
Dim newSmnc As New SiteMapNodeCollection()
smnc = MyBase.GetChildNodes(node)
For Each nd As SiteMapNode In smnc
If nd.Item("displayInMenu") <> "false" Then
newSmnc.Add(nd)
End If
Next
Return newSmnc
End Function
The next step is to get your site to use your provider. To do that, go into the site's Web.config and add a siteMap element. Within this element you nest a providers element which allows you to specify custom configuration for your site's SiteMapProvider. To add your new custom configuration use an add tag specifying the name attribute and the type attribute. You can put whatever you want in the name attribute but you must put the name of your Class file in the type attribute. You must also provide a siteMapFile attribute set to the name of your site map (typically, that will be Web.sitemap).
Finally, you have to tell ASP.NET to use your new configuration. There are a couple of ways to do that but I do it by adding a defaultProvider attribute to the SiteMap element, setting it to the name of my configuration.
Here's an example that creates a configuration called "CustomMenu" and uses the PHVSiteMap class I created earlier:
<siteMap defaultProvider="CustomSiteMap">
<providers>
<add name="CustomSiteMap"
type="PHVSiteMap"
siteMapFile="Web.sitemap"/>
</providers>
</siteMap>
By moving the code to my custom SiteMapProvider I not only centralize my code, I create a class that I can use in other sites (I could even compile it in a separate class library and just reference from other projects). I also now have a base for adding other "enhancements" to the default ASP.NET menuing system.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.