Code - Create a Dynamic MVC Menu

Make a dynamic menu using C# MVC.

I was trying to make a menu that was completely dynamic that could be built from data that cascades horizontally. I found various implementations and kinda did some mixing an matching and thought I would share what I came up with. Hopefully it helps someone out.

First create the data structure. Create your table.

CREATE TABLE [dbo].[MenuItems](
	[MenuItem] [smallint] IDENTITY(1,1) NOT NULL,
	[ParentId] [smallint] NOT NULL,
	[SubParentId] [smallint] NOT NULL,
	[PageId] [int] NOT NULL,
	[MenuText] [varchar](50) NULL,
	[MenuUrl] [varchar](50) NULL)
}

The parent and sub-parent IDs will refer to where on the menu a page will fall (also set them to default to 0 if you choose). When I create a top-level item, I enter the MenuUrl, otherwise if it's a page, it'll be null. When I create pages, on my page form, I have drop-downs for the primary and secondary menu choices, which then get saved to this table (also updates if I change). When creating pages, I save the page's Menu Url to the "pages" table, but I save the text here (you can arrange how you want - I don't remember how I ended up with this arrangement). The ParentId refers to the main menu item and if you want the page to appear in a "sub" menu, the put the MenuItem in the SubParentId column.

Here is the entity class I will use to hold the data:


public class MenuEntity
  {
public short MenuItem;
public short ParentId;
public short SubParentId;
public int PageId;
public string MenuText;
public string MenuUrl;
public string PageUrl;
}
Create a class to create the menu's html. I used a class that's called from my BaseModel so it's in every page.

public MvcHtmlString CreateMenu()
{
}

I keep the menu cached so it doesn't have to be rebuilt every time someone opens a page (but invalidate if I make a change).

string cacheName = "menu";
string menu = (string)HttpContext.Current.Cache[cacheName];
if (!string.IsNullOrWhiteSpace(menu))
{
    return new MvcHtmlString(menu);
}
else
{
    //create menu
}
To build the menu, I start walking through the data looking for the primary menu items (where the ParentId and SubParentId = 0).
     
var MenuItems = new MenuRepository().GetMenuItems();
var primaryLevels = MenuItems.Where(m => m.ParentId == 0 && m.SubParentId == 0);

Now start looping through and writing the data to the stringbuilder 

foreach (MenuEntity primaryEntity in primaryLevels)...
string url = (primaryEntity.MenuUrl != null) ? primaryEntity.MenuUrl : primaryEntity.PageUrl;
html.Append("<ul class=\"menu-top-ul\">");
html.Append("<li><h2><a href=\"/" + url + "\">" + primaryEntity.MenuText + "</a></h2>");

Now it's time to check for any secondary items that may belong to the primary menu item and creating items for them.

var secondaryLevels = MenuItems.Where(m => m.ParentId == primaryEntity.MenuItem && m.SubParentId == 0);

foreach (MenuEntity secondaryEntity in secondaryLevels)...
url = (primaryEntity.MenuUrl != null) ? primaryEntity.MenuUrl : primaryEntity.PageUrl;
url += "/" + ((secondaryEntity.MenuUrl != null) ? secondaryEntity.MenuUrl : secondaryEntity.PageUrl);
html.Append("<li><a href=\"/" + url + "\">" + secondaryEntity.MenuText + "</a>");
Then you check for any third level items and create items for them. You can actually go a lot deeper if you wanted to (notice the SubParentId id is the secondary item from above).


var tertiaryLevels = MenuItems.Where(m => m.ParentId == primaryEntity.MenuItem && m.SubParentId == secondaryEntity.MenuItem);
if (tertiaryLevels.Count() > 0)
{
    html.Append("<ul>");
    foreach (MenuEntity tertiaryEntity in tertiaryLevels){
        url = (tertiaryEntity.MenuUrl != null) ? tertiaryEntity.MenuUrl : tertiaryEntity.PageUrl;
        if (!string.IsNullOrEmpty(secondaryEntity.MenuUrl)){
            html.Append("<li><a href=\"/" + primaryEntity.MenuUrl 
                + "/" + secondaryEntity.MenuUrl + "/" + url + "\">" 
                + tertiaryEntity.MenuText + "</a></li>");
        }
        else {
            html.Append("<li><a href=\"/" + primaryEntity.MenuUrl + "/" 
                + url + "\">" + tertiaryEntity.MenuText + "</a></li>");
        }
    }
    html.Append("</ul>");
}
If everything worked, save to cache and return the string. Be sure to close the list items.

Here's the full code:
public MvcHtmlString CreateMenu()
{
string cacheName = "menu";
string menu = Helpers.IsDevelopment ? null : (string)HttpContext.Current.Cache[cacheName]; if (!string.IsNullOrWhiteSpace(menu))
{
return new MvcHtmlString(menu);
 }
else
{
var html = new StringBuilder();
IEnumerable<MenuEntity> MenuItems = new MenuRepository().GetMenuItems();
IEnumerable<MenuEntity> primaryLevels = MenuItems.Where(m => m.ParentId == 0 && m.SubParentId == 0); string url = "";
foreach (MenuEntity primaryEntity in primaryLevels) {
url = (primaryEntity.MenuUrl != null) ? primaryEntity.MenuUrl : primaryEntity.PageUrl;
html.Append("<ul class=\"menu-top-ul\">");
html.Append("<li><h2><a href=\"/" + url + "\">" + primaryEntity.MenuText + "</a></h2>"); IEnumerable<MenuEntity> secondaryLevels = MenuItems.Where(m => m.ParentId == primaryEntity.MenuItem && m.SubParentId == 0); if (secondaryLevels.Count() > 0)
{
html.Append("<ul>");
foreach (MenuEntity secondaryEntity in secondaryLevels) {
url = (primaryEntity.MenuUrl != null) ? primaryEntity.MenuUrl : primaryEntity.PageUrl; url += "/" + ((secondaryEntity.MenuUrl != null) ? secondaryEntity.MenuUrl : secondaryEntity.PageUrl); html.Append("<li><a href=\"/" + url + "\">" + secondaryEntity.MenuText + "</a>"); IEnumerable<MenuEntity> tertiaryLevels = MenuItems.Where(m => m.ParentId == primaryEntity.MenuItem && m.SubParentId == secondaryEntity.MenuItem); if (tertiaryLevels.Count() > 0) { html.Append("<ul>"); foreach (MenuEntity tertiaryEntity in tertiaryLevels) { url = (tertiaryEntity.MenuUrl != null) ? tertiaryEntity.MenuUrl : tertiaryEntity.PageUrl; if (!string.IsNullOrEmpty(secondaryEntity.MenuUrl)) { html.Append("<li><a href=\"/" + primaryEntity.MenuUrl + "/" + secondaryEntity.MenuUrl + "/" + url + "\">" + tertiaryEntity.MenuText + "</a></li>"); } else { html.Append("<li><a href=\"/" + primaryEntity.MenuUrl + "/" + url + "\">" + tertiaryEntity.MenuText + "</a></li>"); } }
html.Append("</ul>"); } html.Append("</li>"); } html.Append("</ul>"); } html.Append("</li>"); html.Append("</ul>"); } if (html.Length > 0) { HttpContext.Current.Cache.Insert(cacheName, html.ToString(), null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(60)); }
return new MvcHtmlString(html.ToString()); } }