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

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

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

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

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

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

4. To support submenu inside a dropdown menu, append the following CSS to ~/Content/Site.css

/* Bootstrap Dropdown Submenu */
.dropdown-submenu {
	position: relative;
}

.dropdown-submenu > .dropdown-menu {
	top: 0;
	left: 100%;
	margin-top: -6px;
	margin-left: -1px;
	-webkit-border-radius: 0 6px 6px 6px;
	-moz-border-radius: 0 6px 6px 6px;
	border-radius: 0 6px 6px 6px;
}

.dropdown-submenu:hover > .dropdown-menu {
	display: block;
}

.dropdown-submenu > a:after {
	display: block;
	content: " ";
	float: right;
	width: 0;
	height: 0;
	border-color: transparent;
	border-style: solid;
	border-width: 5px 0 5px 5px;
	border-left-color: #cccccc;
	margin-top: 5px;
	margin-right: -10px;
}

.dropdown-submenu:hover > a:after {
	border-left-color: #ffffff;
}

.dropdown-submenu.pull-left {
	float: none;
}

.dropdown-submenu.pull-left > .dropdown-menu {
	left: -100%;
	margin-left: 10px;
	-webkit-border-radius: 6px 0 6px 6px;
	-moz-border-radius: 6px 0 6px 6px;
	border-radius: 6px 0 6px 6px;
}

.navbar-nav .divider-vertical {
    height: 40px;
    margin: 0 9px;
    border-left: 1px solid #f2f2f2;
    border-right: 1px solid #ffffff;
}

5. 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)
{
    <nav class="navbar navbar-default" role="navigation">
        <div class="container-fluid">
            <div class="collapse navbar-collapse">
                <ul class="nav navbar-nav">
                    @foreach (SiteMapNodeModel node in nodeList)
                    {
                        string url = node.IsClickable ? node.Url : "#";

                        if (!node.Children.Any())
                        {
                            <li><a href="@url">@node.Title</a></li>
                        }
                        else
                        {
                            <li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown">@node.Title <span class="caret"></span></a>@DropDownMenu(node.Children)</li>
                        }

                        if (node != nodeList.Last())
                        {
                            <li class="divider-vertical"></li>
                        }
                    }
                </ul>
            </div>
        </div>
    </nav>
}

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

            string url = node.IsClickable ? node.Url : "#";

            if (!node.Children.Any())
            {
                <li><a href="@url">@node.Title</a></li>
            }
            else
            {
                <li class="dropdown-submenu"><a href="@url">@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).

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

......
<body>
	<div class="container">
		<div class="row">
			<div class="span12">
				<nav>
					@Html.MvcSiteMap().Menu("BootstrapMenuHelperModel")
				</nav>
			</div>
		</div>
		<!-- //row -->
		<div class="row">
			<div class="span12 body-content">
				@RenderBody()
				<hr />
				<footer>
					<p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
				</footer>
			</div>
		</div>
		<!-- //row -->
	</div>
    ......
</body>
......

7. Completed!!!

DropDown Menu
DropDown Menu
DropDown Menu w/ Separator
DropDown Menu w/ Separator

You can download the code sample here.

References

Advertisements

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

  1. Nice article.

    One question though, how do you make a menu item invisible or greyed out depending on user role? For example, show the Administration node only to users with Admin role.

    Thanks.

  2. Interesting article, thank you.

    You have to use a sitemap file? Or that could be provided via Mvc Controller?

    Re: an earlier posters concerns, it should be fairly (?) easy to connect with, say, Identity Framework, or analog to IF, for purposes of determining roles, permissions, and include only what the end user has access to?

  3. What about the mobile version of bootstrap menus. When it makes the hamburger collapsed menu icon. Do you have an css that will create the same thing?

  4. Hi,
    First of all, thanks for this post. It’s helping me a lot!
    I have one question:
    I’m trying to implement but I’m receiving a error telling me that “SiteMapNodeModelList” does not contain a definition for “Any()” and “Last()”.
    Do you know how to fix this?

      1. I didn’t downloaded your code.
        .
        .
        .
        .
        I download it now and I figured out what was going on. The first level in my sitemap in the XML file wasn’t a simple “Home”. I was trying to make the first item on the menu to be a complex element:

        Then, after analise your code, I put the sitemap “Compras” inside a “Home” sitemap and it worked.
        I had to do an adaptation in BootstrapMenuHelperModel.cshtml to include the dropdown-header element:

        if (node.Description == “subtitulo”)
        {
        @node.Title
        continue;
        }

        Thanks for your reply!

  5. I am trying to follow this tutorial, after i have Install-Package MvcSiteMapProvider.MVC5
    my partial view still could not find a reference to MvcSiteMapProvider,SiteMapNodeModel,nodeList.Last(). Please how do i fix this?

  6. first of all thanks a lot for the post
    its working fine but there is an issue when resize browser window menu disappear
    please check & do reply

    1. One question though, how do you make a menu item invisible or greyed out depending on user role? For example, show the Administration node only to users with Admin role.

      Thanks.

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