Menus are one of the most basic elements on any website, but there’s not a simple dynamic menu out of the box in ASP.NET MVC. There are several third party solutions you can add as an HTML helper or in a controller, but in several projects I’ve needed a simple and quick way to generate a menu. Here’s an updated version of something I wrote a while ago in just such an occasion.
We’ll use VS 2010 and the new jQuery Menu widget for the UI (the standard menu CSS won’t work). You can use any CSS or JavaScript menu UI you want to–I’m using jQuery because it’s free and easy. You can find the Git repo for this project at https://bitbucket.org/rjdudley/simple-dynamic-menu-in-mvc.
1. Start by creating a new ASP.NET MVC 4 Internet Application.
2. The default template installs jQuery 1.7.1 and jQuery 1.8.2, but the menu widget was released in jQuery 1.9 and jQuery UI 1.10. The easiest way to update jQuery and jQuery UI is via NuGet. Install both the latest jQuery and jQuery UI.
One glitch in this sample is the jQuery menu widget–this is a vertical menu. A horizontal menu will be in the menubar widget, which has not yet been released. It was supposed to be in 1.9 but didn’t make the cut. Not a big deal, since this isn’t a post about the UI, but fair warning.
3. Let’s make the jQuery menu work first. Open Content/Site.css, and find around line 180 where the menu styles are. Comment out all of the menu styles, it should be about 25 lines of code total. These styles will conflict with the jQuery menu.
4. To help prevent page flash oafter loading and to make sure all CSS elements are in place before a script is executed, we load CSS files at the top of the page. Just below the Styles.Render directive, add links for jquery.ui.core.css, then jquery.ui.menu.css. The code should look like this:
@Styles.Render("~/Content/css") <link href="../../Content/themes/base/jquery.ui.core.css" rel="stylesheet" type="text/css" /> <link href="../../Content/themes/base/jquery.ui.menu.css" rel="stylesheet" type="text/css" /> @Scripts.Render("~/bundles/modernizr")
5. Next, open up layout.cshtml and scroll down to the bottom of the file. Add a Scripts.Render directive for jQuery UI underneath the one for jQuery, then a small script block to call the jQuery menu function. The code should look like this:
@Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/jqueryui") <script> $(document).ready(function () { $("#menu").menu(); }); </script> @RenderSection("scripts", required: false)
(If you want to see how these bundles are defined, open up App_Start/BundleConfig.cs. Later on you may want to configure all of these scripts and CSS into a bundle)
6. If we load and run the page now, we should now have a fully functional vertical menu. Good enough for this demo.
7. We need a model for a MenuItem, so create MenuItem.cs with the following code:
using System; using System.Collections.Generic; namespace SimpleMenuSample.Models { public class MenuItem { public MenuItem() { this.ChildMenuItems = new List<MenuItem>(); } public int MenuItemId { get; set; } public string MenuItemName { get; set; } public string MenuItemPath { get; set; } public Nullable<int> ParentItemId { get; set; } public virtual ICollection<MenuItem> ChildMenuItems { get; set; } } }
8. Now we need a model for the Menu, so create menu.cs with the following code:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace SimpleMenuSample.Models { public class Menu { public Menu() { Items = new List<MenuItem>(); } public List<MenuItem> Items; } }
9. Now that we have our models defined, it’s time to add a controller method to generate the menu. Normally you’d do some data access, but we’re going to hard code the menu items here for the sake of simplicity. Open HomeController.cs and add the following code:
[ChildActionOnly] public ActionResult Menu() { var _menu = new Menu(); // Normally you'd do some data or cache access to build/retrieve the user's menu // then you're loop through the results and build the menu object // we're hard coding for the sake of simplicity var _google = new MenuItem() { MenuItemName = "Google", MenuItemPath = "http://google.com/", }; _google.ChildMenuItems.Add(new MenuItem() { MenuItemName = "Google Images", MenuItemPath = "http://google.com/images/" }); var _bing = new MenuItem() { MenuItemName = "Bing", MenuItemPath = "http://bing.com/" }; _bing.ChildMenuItems.Add(new MenuItem() { MenuItemName = "Bing Images", MenuItemPath = "http://bing.com/images/" }); _menu.Items.Add(_google); _menu.Items.Add(_bing); return PartialView("_Menu", _menu); }
10. Now that we have a model and a controller, we need a view to display our menu. Create a shared view named _Menu.cs with the following code:
@model SimpleMenuSample.Models.Menu <ul id="menu"> @foreach (var item in Model.Items) { <li><a href="@item.MenuItemPath">@item.MenuItemName</a> @if (item.ChildMenuItems.Any()) { <ul> @foreach (var subitem in item.ChildMenuItems) { <li><a href="@subitem.MenuItemPath">@subitem.MenuItemName</a></li> } </ul> } </li> } </ul>
11. One final change is to call the menu from _Layout.cshtml. Change the nav element to look like the code below:
<nav> @Html.Action("Menu","Home") </nav>
And that should be a bingo! When you run the project, you should see a (rather ugly) vertical menu (seen below). Hovering over one of the options should cause the child entry to appear.
Hope this helps!