Providing Website Navigation with SiteMaps

This is the Visual Basic tutorial    (Switch to the Visual C# tutorial)

Learn how to take advantage of SiteMaps to describe the navigational structure of your website. In this tutorial, you learn how to create a custom Menu HTML helper that generates menu links from a SiteMap automatically.

Providing Website Navigation with SiteMaps (VB)

A SiteMap enables you to describe the navigational structure of a website separate from how the URLs of your website are exposed through controllers and controller actions. A SiteMap enables you to describe how pages in an ASP.NET MVC application are related for purposes of navigation.

You can use a SiteMap, in combination with the SiteMap API, to generate the navigational links for your website. For example, you can use SiteMaps to generate menus, tabs, tree views, previous and next links, and breadcrumb trails. In this tutorial, I demonstrate how to create a simple Menu() HTML helper that generates menu links from a SiteMap automatically.

In an ASP.NET Web Forms application, you can use a feature of SiteMaps called security trimming to display only those navigational links that a user is authorized to view. For example, when security trimming is enabled, you can hide an Admin link from any users who are not authorized to access the Admin page. Security trimming, by default, is not supported within an ASP.NET MVC application.

Creating a SiteMap

The easiest way to create a SiteMap is to create an XML file that describes the navigational structure of your website. You can create a new SiteMap in Visual Studio by selecting the menu option Project, Add New Item, and add a SiteMap file (see Figure 1). In order for your SiteMap file to work without additional configuration, you should name your new SiteMap file Web.SiteMap and you should locate the SiteMap file in the root of your application.

Figure 1 – Adding a new XML SiteMap file

clip_image002

An XML SiteMap file contains a root <siteMap> element that contains one or more <siteMapNode> elements. You can nest one <siteMapNode> element within another <siteMapNode> element. You use these <siteMapNode> elements to describe the relationship between the pages in your ASP.NET MVC application.

Each <siteMapNode> element can have the following attributes (this is not a complete list):

  • url – The URL of the page. Used when generating the URL for a navigation link.
  • title – The title of the page. Used when generating the title for a navigation link.
  • Description – The description of the page. Used when generating a description associated with a navigation link.
  • resourceKey – A resource key that you can use to localize the siteMapNode to multiple languages
  • siteMapFile – The path to another SiteMap file. Used when dividing a SiteMap into multiple SiteMap files.

XML documents are case-sensitive. So, there is a difference between <siteMapNode> and <SiteMapNode>.

The SiteMap file in Listing 1 contains four <siteMapNode> elements. It describes a website that contains a Home page and four top-level pages entitled Products, Services, and About.

Listing 1 – Web.sitemap

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
    <siteMapNode 
        url="~/" 
        title="Home"  
        description="The Home Page">
        <siteMapNode 
            url="~/Product/Index" 
            title="Products"  
            description="Our Products" />
        <siteMapNode 
            url="~/Service/Index" 
            title="Services"  
            description="Our Services" />
        <siteMapNode
            url="~/Home/About"
            title="About" 
            description="About Us" />

    </siteMapNode>
</siteMap>

Understanding the SiteMap API

You interact with a SiteMap through the SiteMap API. The SiteMap API is represented by the static SiteMap class. Because the SiteMap class is static, you can access the class from anywhere within an ASP.NET MVC application without doing anything special (you can access the class directly within controllers, views, helpers, model classes, and so on).

The static SiteMap class has two important properties:

  • CurrentNode – Returns the SiteMapNode that corresponds to the user’s current location in the website.
  • RootNode – Returns the root SiteMapNode.

You use the SiteMap class to determine where you are within a SiteMap. You use the properties and methods of the SiteMapNode class to generate navigational links. Here are some of the more interesting properties of the SiteMapNode class:

  • ChildNodes – Returns all of the child SiteMapNodes
  • Description – Returns the description of a SiteMapNode
  • NextSibling – Returns the next sibling SiteMapNode
  • ParentNode – Returns the parent SiteMapNode
  • PreviousSibling – Returns the previous sibling SiteMapNode
  • Title – Returns the title of a SiteMapNode
  • Url – Returns the URL of a SiteMapNode

Creating a Menu HTML Helper

You can take advantage of SiteMaps, and the SiteMap API, to create HTML Helpers that generate navigational links automatically. For example, the HTML Helper in Listing 2 generates a menu from a SiteMap.

Listing 2 – Helpers\MenuHelper.vb

Imports System.Runtime.CompilerServices

Public Module MenuHelper

    <Extension()> _
    Function Menu(ByVal helper As HtmlHelper) As String
        Dim sb As New StringBuilder()

        ' Create opening unordered list tag
        sb.Append("<ul class='menu'>")

        ' Render each top level node
        Dim topLevelNodes = SiteMap.RootNode.ChildNodes
        For Each node As SiteMapNode In topLevelNodes
            If SiteMap.CurrentNode Is node Then
                sb.AppendLine("<li class='selectedMenuItem'>")
            Else
                sb.AppendLine("<li>")

            End If
            sb.AppendFormat("<a href='{0}'>{1}</a>", node.Url, helper.Encode(node.Title))
            sb.AppendLine("</li>")
        Next

        ' Close unordered list tag
        sb.Append("</ul>")

        Return sb.ToString()
    End Function


End Module

Listing 2 contains an extension method named Menu() that extends the HtmlHelper class. This method grabs all of the child nodes of the root node in the SiteMap and renders a list of links. The links are rendered in an HTML unordered list <ul> tag.

The SiteMap.CurrentNode property is used to determine whether a link being rendered corresponds to the current location of the user. The current node is marked with the Cascading Style Sheet selectedMenuItem class.

You can use the Menu() HTML helper in a particular view. However, it makes more sense to call the Menu() helper method within a View Master Page. That way, the menu will appear in all of the views in your application.

The View Master Page in Listing 3 illustrates how you can use the Menu() helper method.

Listing 3 – Site.master

<%@ Master Language="VB" Inherits="System.Web.Mvc.ViewMasterPage" %>
<%@ Import Namespace="MvcApplication1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title></title>
    <style type="text/css">
    
    .menu
    {
        list-style:none;
        padding:0px;
        margin:0px;
    }
    
    .menu li
    {
        float:left;
    }

    .menu a
    {
        display:block;
        background-color:#eeeeee;
        color:Black;
        font-weight:bold;
        padding:4px;
        border:solid 1px black;
        text-decoration:none;
        margin:2px;
    }

    .selectedMenuItem a 
    {
        background-color: White;
    } 
        
    </style>

</head>
<body>
    <div>
    
        <%= Html.Menu() %>
    
        <br style="clear:both" />
        
        <asp:ContentPlaceHolder 
            ID="MainContent" 
            runat="server" />
            
    </div>
</body>
</html>

Notice that the namespace MvcApplication1.Helpers is imported at the top of the View Master Page. You must import the namespace of the extension method in order for the extension method to appear as a method of the Html property.

Notice that the View Master Page contains a Cascading Style Sheet that is used to style the menu links rendered by the Menu() helper. This style sheet is used to format the unordered list and links to look like a tab strip (see Figure 2).

When you click a menu link, the selected menu link is highlighted with the style defined by the selectedMenuItem CSS class. In the case of Listing 3, the selected menu item is displayed with a white background color and unselected menu items are displayed with a gray background color.

Figure 2 – The rendered Menu helper

clip_image004

The Importance of Canonical URLs

In an ASP.NET MVC application, multiple URLs can be mapped to the same controller action. For example, by default, you can invoke the Index() action on the Home controller by requesting any of the following URLs:

http://www.MySite.com/

http://www.MySite.com /Home

http://www.MySite.com /Home/Index

The default routes set up in the Global.asax file map all three of these URLs to the same controller action. Furthermore, you can invoke the Index action exposed by the Product controller by requesting either of the following URLs:

http://www.MySite.com/Product/

http://www.MySite.com/Product/Index

This feature of the ASP.NET MVC framework causes problems when you use SiteMaps. When you create a standard XML SiteMap file, you can associate each SiteMapNode with only one URL. For example, you can associate only one URL with the Index action of the Home controller. Alternative URLs for the same action won’t match the correct SiteMapNode. So what do you do?

One option is to modify the routes defined in the Global.asax file so that multiple URLs cannot be mapped to the same controller action. For example, instead of defining the Default route like this:

routes.MapRoute( _
  "Default", _
  "{controller}/{action}/{id}", _
  New With {.controller = "Home", .action = "Index", .id = ""} _
)

You can replace the Default route definition with the following two route definitions like this:

routes.MapRoute( _
   "Home", _
   "", _
   New With {.controller="Home", .action="Index", .id=""} _
)

routes.MapRoute( _
   "Default", _
   "{controller}/{action}/{id}", _
    New With {.id = ""} _
)

The first route definition maps the URL http://www.MySite.com/ to the Index action of the Home controller. The second route definition maps a URL such as http://www.MySite.com/Product/Index to the Index action of the Product controller.

These modified routes won’t match a URL like http://www.MySite.com/Home/Index or http://www.MySite.com/Product. The modified routes map one and only one URL to a controller action. In other words, you are forced to use canonical URLs for all of your controller actions.

Another solution to the problem of canonical URLs is to take advantage of the Internet Information Services 7.0 URL Rewrite Module. Learn more at http://learn.iis.net/page.aspx/460/using-url-rewrite-module/

Summary

In this tutorial, you learned how to use SiteMaps to describe the navigational structure of your ASP.NET MVC websites. You learned how to create new XML SiteMap files and interact with the SiteMap API. Finally, you learned how to create a custom Menu() HTML helper that generates website menu links from a SiteMap automatically.

Visual Basic Tutorials

(Switch to Visual C# tutorials)

Microsoft Communities