ASP.NET MVC Menu using Site Map Provider & Bootstrap Navbar

Updated Blog Post: ASP.NET MVC 5 Menu using Site Map Provider & Bootstrap 3 Navbar

1. Install Bootstrap for MVC 4 NuGet package. This package will install Twitter Bootstrap to the project and add bundling and minification to application start.

2. Install MvcSiteMapProvider NuGet package. This package is a SiteMapProvider implementation for the ASP.NET MVC framework.

3. In web.config, downsize the MvcSiteMapProvider configuration to the minimal. Keep it simple, stupid. (KISS)

<system.web>
  <siteMap defaultProvider=&amp;amp;quot;MvcSiteMapProvider&amp;amp;quot;>
    <providers>
      <clear />
      <add name=&amp;amp;quot;MvcSiteMapProvider&amp;amp;quot; type=&amp;amp;quot;MvcSiteMapProvider.DefaultSiteMapProvider, MvcSiteMapProvider&amp;amp;quot; siteMapFile=&amp;amp;quot;~/Mvc.Sitemap&amp;amp;quot; />
    </providers>
  </siteMap>
</system.web>

4. Modify the ~\Mvc.sitemap file as the following.

<?xml version=&amp;amp;quot;1.0&amp;amp;quot; encoding=&amp;amp;quot;utf-8&amp;amp;quot; ?>
<mvcSiteMap xmlns:xsi=&amp;amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;amp;quot;
            xmlns=&amp;amp;quot;http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-3.0&amp;amp;quot;
            xsi:schemaLocation=&amp;amp;quot;http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-3.0 MvcSiteMapSchema.xsd&amp;amp;quot;
            enableLocalization=&amp;amp;quot;true&amp;amp;quot;>
  <mvcSiteMapNode title=&amp;amp;quot;Home&amp;amp;quot; controller=&amp;amp;quot;Home&amp;amp;quot; action=&amp;amp;quot;Index&amp;amp;quot;>
    <mvcSiteMapNode title=&amp;amp;quot;About&amp;amp;quot; controller=&amp;amp;quot;Home&amp;amp;quot; action=&amp;amp;quot;About&amp;amp;quot; />
    <mvcSiteMapNode title=&amp;amp;quot;Contact&amp;amp;quot; controller=&amp;amp;quot;Home&amp;amp;quot; action=&amp;amp;quot;Contact&amp;amp;quot; />
    <mvcSiteMapNode title=&amp;amp;quot;Administration&amp;amp;quot; clickable=&amp;amp;quot;false&amp;amp;quot;>
      <mvcSiteMapNode title=&amp;amp;quot;User Mgmt&amp;amp;quot; controller=&amp;amp;quot;Home&amp;amp;quot; action=&amp;amp;quot;UserMgmt&amp;amp;quot; />
      <mvcSiteMapNode title=&amp;amp;quot;Role Mgmt&amp;amp;quot; controller=&amp;amp;quot;Home&amp;amp;quot; action=&amp;amp;quot;RoleMgmt&amp;amp;quot; />
    </mvcSiteMapNode>
    <mvcSiteMapNode title=&amp;amp;quot;Profile&amp;amp;quot; clickable=&amp;amp;quot;false&amp;amp;quot;>
      <mvcSiteMapNode title=&amp;amp;quot;Change Password&amp;amp;quot; controller=&amp;amp;quot;Home&amp;amp;quot; action=&amp;amp;quot;ChangePassword&amp;amp;quot; />
      <mvcSiteMapNode title=&amp;amp;quot;Separator&amp;amp;quot; clickable=&amp;amp;quot;false&amp;amp;quot; />
      <mvcSiteMapNode title=&amp;amp;quot;Sign Off&amp;amp;quot; controller=&amp;amp;quot;Home&amp;amp;quot; action=&amp;amp;quot;SignOff&amp;amp;quot; />
    </mvcSiteMapNode>
  </mvcSiteMapNode>
</mvcSiteMap>

5. The Twitter Bootstrap package installed in step 1 wouldn’t add the style and script rendering to the layout file. You need to do it manually by modifying the _Layout.cshtml file as following.

<!DOCTYPE html>
<html>
<head>
  <meta charset=&amp;amp;quot;utf-8&amp;amp;quot; />
  <meta name=&amp;amp;quot;viewport&amp;amp;quot; content=&amp;amp;quot;width=device-width&amp;amp;quot; />
  <title>@ViewBag.Title</title>
  @Styles.Render(&amp;amp;quot;~/Content/css&amp;amp;quot;)
  @Styles.Render(&amp;amp;quot;~/Content/bootstrap&amp;amp;quot;)
  @Scripts.Render(&amp;amp;quot;~/bundles/modernizr&amp;amp;quot;)
</head>
<body>
  @RenderBody()

  @Scripts.Render(&amp;amp;quot;~/bundles/jquery&amp;amp;quot;)
  @Scripts.Render(&amp;amp;quot;~/bundles/bootstrap&amp;amp;quot;)
  @RenderSection(&amp;amp;quot;scripts&amp;amp;quot;, required: false)
</body>
</html>

6. The MvcSiteMapProvider renders a menu as a unordered list which doesn’t fit the Bootstrap’s Navbar component. We need to create a partial view to fill this gap. Add a partial view file BootstrapMenuHelperModel.cshtml under ~\Views\Shared\DisplayTemplates

@model MvcSiteMapProvider.Web.Html.Models.MenuHelperModel
@using System.Web.Mvc.Html
@using MvcSiteMapProvider.Web.Html.Models

@helper  TopMenu(List<SiteMapNodeModel> nodeList)
{
  @:<div class=&amp;amp;quot;navbar&amp;amp;quot;><div class=&amp;amp;quot;navbar-inner&amp;amp;quot;><ul class=&amp;amp;quot;nav&amp;amp;quot;>
  foreach (SiteMapNodeModel node in nodeList)
  {
    string url = node.IsClickable ? node.Url : &amp;amp;quot;#&amp;amp;quot;;

    if (!node.Children.Any())
    {
      @:<li><a href=&amp;amp;quot;@url&amp;amp;quot;>@node.Title</a></li>
    }
    else
    {
      @:<li class=&amp;amp;quot;dropdown&amp;amp;quot;><a class=&amp;amp;quot;dropdown-toggle&amp;amp;quot; data-toggle=&amp;amp;quot;dropdown&amp;amp;quot; href=&amp;amp;quot;@url&amp;amp;quot;>@node.Title <b class=&amp;amp;quot;caret&amp;amp;quot;></b></a>@DropDownMenu(node.Children)</li>
    }

    if (node != nodeList.Last())
    {
      @:<li class=&amp;amp;quot;divider-vertical&amp;amp;quot;></li>
    }
  }

  @:</ul></div></div>
}

@helper DropDownMenu(SiteMapNodeModelList nodeList)
{
  <ul class=&amp;amp;quot;dropdown-menu&amp;amp;quot;>
  @foreach (SiteMapNodeModel node in nodeList)
  {
    if (node.Title == &amp;amp;quot;Separator&amp;amp;quot;)
    {
      @:<li class=&amp;amp;quot;divider&amp;amp;quot;></li>
      continue;
    }

    string url = node.IsClickable ? node.Url : &amp;amp;quot;#&amp;amp;quot;;

    if (!node.Children.Any())
    {
      @:<li><a href=&amp;amp;quot;@url&amp;amp;quot;>@node.Title</a></li>
    }
    else
    {
      @:<li class=&amp;amp;quot;dropdown-submenu&amp;amp;quot;><a href=&amp;amp;quot;@url&amp;amp;quot;>@node.Title</a>@DropDownMenu(node.Children)</li>
    }
  }
  </ul>
}

@TopMenu(Model.Nodes)

Notes: 2 value-added features worth mention in the above code snippet. a) Handle multi-level site map using Bootstrap’s Dropdowns component (This post shows 2 levels site map, I tested with 4 levels site map); b) Support adding Separator in the menu (Refer to above ~\Mvc.sitemap snippet in step 4).

7. Add HomeController, and then add Index View for testing.

8. Edit the _Layout.cshtml file again to render the MvcSiteMap’s menu using the Bootstrap menu partial view.

......
<body>
    <div class=&amp;amp;quot;container&amp;amp;quot;>
        <div class=&amp;amp;quot;row&amp;amp;quot;>
            <div class=&amp;amp;quot;span12&amp;amp;quot;>
                <nav>
                    @Html.MvcSiteMap().Menu(&amp;amp;quot;BootstrapMenuHelperModel&amp;amp;quot;)
                </nav>
            </div>
        </div>
        <!-- //row -->
        <div class=&amp;amp;quot;row&amp;amp;quot;>
            <div class=&amp;amp;quot;span12&amp;amp;quot;>
                @RenderBody()
            </div>
        </div>
        <!-- //row -->
    </div>
    ......
</body>
......

9. Completed!!!
Drop Down Menu
DropDown Menu

Drop Down Menu w/ Separator
DropDown Menu w/ Separator

You can download the code sample here.

References

Advertisements

16 thoughts on “ASP.NET MVC Menu using Site Map Provider & Bootstrap Navbar

  1. Great stuff – thanks for this. I’m just curious, maybe I am missing it – is there a way to render submenus (not dropdown from the main menu). Scenario: I’m viewing my users profile page, and I want tabs available that map to controller actions, maybe something like (Edit, Friends, Settings, etc…). Is there anything built in for this in MvcSiteMapProvider? Or a way to specify what level, or menu item, to start at?

  2. I am unable to reproduce it using MVC 4 and .NET framework 4.0 using visual studio 2010??? please help me .I installed MVC 4 on VS2010 ,Let us this may be cause… I am getting Nvigation routes error

  3. I am trying with MVC5 and having problem with “@:” insite if blocks.
    @:@node.Title
    error – @ must be followed by a valid code block
    “:” is not valid at the start of a code block. Only identifiers, keywords, comments, “(” and “{” are valid.
    How to fix it ?

    1. Here is the fixed code:

      @helper TopMenu(List nodeList) {

      @foreach (SiteMapNodeModel node in nodeList) {
      string url = node.IsClickable ? node.Url : “#”;

      if (!node.Children.Any()) {
      @:
      @node.Title
      } else {
      @:
      @node.Title @DropDownMenu(node.Children)
      }

      if (node != nodeList.Last()) {
      @:

      }
      }

      }

  4. Hi,

    This is my first time using MvcSiteMapProvider. I am wondering if you can create another sitemap file say “xProject.sitemap” then use it instead of using the default sitemap generated Mcv.sitemap.

    Can anyone teach me how or provide links? I searched the internet but I was not lucky enough. There must be some kind of feature like that.

    Thanks a lot!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s