Practical ASP.NET

Managing Menus

Peter considers two solutions for keeping items in the sitemap off of your Menu or TreeView controls. But he's also wondering if there are more solutions out there.

I was recently asked by one of the participants taking an ASP.NET course with me if there was a way to prevent an item in the sitemap from appearing on the menu but still have it appear on SiteMapPath (the control that gives you a "breadcrumbs-like" description of where a page fits into the site's hierarchy). I never ask why my course participants why they want to do these things... I just assume there's a good reason and try to be helpful as I can. Then I send them my bill.

I could think of at least two solutions to the problem. I'll cover one in this column (working with the menu control) and one in the next column (creating my own menu provider). However, I'm suspicious that there are other, probably simpler, solutions out there that I haven't considered. If you've got one, post it!

A quick review: To add a menu to a page, you first add a Web.sitemap file to your project with an XML description of the menus and submenus you want. Here's the start of a typical sitemap that defines the root for the menu ("Home"), a main menu item ("Management") and the first submenu item on that menu item ("List Customers"):

?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns=
   "http://schemas.microsoft.com/AspNet/SiteMap-File-1.0">
 <siteMapNode url="~/Default.aspx" 
              title="Home"  
              description="Northwind CRM home">
    <siteMapNode title="Management" 
                description="Customer Support">
       <siteMapNode url="~/CustomerList.aspx" 
                    title="List Customers"                
                    description="Customers for region"/>

You then drag the control of your choice (a Menu or a TreeView) onto your page and, from its SmartTag, create a SiteMapDataSource that ties your menu control to the sitemap. Typically, you'll put your menu on the Master Page for your site.

To interfere with what goes onto the menu you want to add code to the menu's MenuItemDataBound event. That event's e parameter passes a ton of information that you can use to control what actually gets displayed. The e parameter's Item property, for instance, gives you access to the MenuItem itself which, in term, gives you access to the MenuItem's parent. Once you have a MenuItem's parent, you can remove the item from the parent's ChildItems collection with the collection's Remove method.

To remove each menu item as it's added, for instance, you could use this code which checks to see if a menu item has a parent before trying to remove it:

If e.Item.Parent IsNot Nothing Then
  e.Item.Parent.ChildItems.Remove(e.Item)
End If

This leaves the root menu item ("Home" in my example) still on the screen. So, to complete this process, the final step is to check to see if the menu has any children under its root and set the menu's Visible property to False if it does not. This code, in the Menu's PreRender event will do the job:

If Me.Menu1.Items(0).ChildItems.Count = 0 Then
  Me.Menu1.Visible = False
End If

While a neat trick, this didn't solve my participant's problem: He only wanted some of the items deleted. There are a couple of ways that could be handled. For instance, if we wanted to check an attribute of the SiteMapNode that generated the menu item, I could convert the MenuItem's DataItem property to a SiteMapNode and check its Url property. This example, for instance, eliminates any MenuItem with a URL that contains "Customer":

Dim smn As SiteMapNode
smn = CType(e.Item.DataItem, SiteMapNode)
If smn.Url.Contains("Customer") Then
  e.Item.Parent.ChildItems.Remove(e.Item)
End If

But, for maximum flexibility, I'd like to control this process through some custom attribute that I add to the SiteMapNode in the sitemap. I'd like to be able to add something like this displayInMenu attribute:

<siteMapNode url="~/CustomerList.aspx"
             title="List Customers"
             description="Customers for region"
             displayInMenu="false"/>

Of course, you could also add some security-related attribute or, perhaps, an attribute related to the user's level of expertise (e.g. "novice", "expert").

Fortunately, the SiteMapNode object has an Item collection that is used for nothing else than retrieving custom attributes (you can't use it to retrieve the standard attributes like url, for instance). If the attribute you ask for doesn't exist on the SiteMapNode no error is raised so this code does the job:

Dim smn As SiteMapNode
smn = CType(e.Item.DataItem, SiteMapNode)
If smn.Item("displayInMenu") = "false" Then
  e.Item.Parent.ChildItems.Remove(e.Item)
End If

I only wish I'd thought of this much detail when the question was asked.

Next column: Building your own menu provider.

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/.

comments powered by Disqus

Featured

Subscribe on YouTube