views:

714

answers:

1

I'm looking for a way to handle a database-driven menu in ASP.NET MVC that doesn't violate the MVC principles. I want to replace the hard-coded, default, "Home, About" menu with something from my database. How would I wire this up? Would I just set up a ContentPlaceHolder in my Site.Master and have regenerated in my Views? That doesn't seem right to me.

+2  A: 

My main menu is a ViewUserControl that is rendered as a partial view in my MasterPage. Although mine is hard-coded, you could easily generate it from ViewData. Generating it from view data would probably involve implementing either a custom FilterAttribute that specified the parameters to use in generating the menu that would be applied to each controller/action or, if the menu is the same on each page, implementing a base controller that fills in the view data by overriding OnActionExecuted and adding to the ViewData in it.

Example (note, you'd probably use caching for the results instead of getting them from the db each time).

Model classes

public class MenuItem
{
    public string Text { get; set; }
    public string Action { get; set; }
    public string Controller { get; set; }
}

public class Menu
{
     public string Heading { get; set; }
     public IEnumerable<MenuItem> Items { get; set; }
}

MenuControl.ascx : of type System.Web.Mvc.ViewPage<List<Menu>>

<div id="mainMenu">
<% foreach (var menu in Model) { %>
   <div class="menu">
      <h2 class="menu-heading"><%= menu.Heading %></h2>
      <% foreach (var item in Model.Items) { %>
         <%= Html.ActionLink( item.Text,
                              item.Action,
                              item.Controller,
                              null,
                              { @class = "menu-item" } ) %>
      <% } %>
   </div>
<% } %>
</div>

MasterPage

<html>
<head>
...
<asp:ContentPlaceHolder runat="server" id="HeaderContent">
</head>
<body>

... other HTML...

<% Html.RenderPartial( "MenuControl", ViewData["mainMenu"], ViewData ); %>

<asp:ContentPlaceHolder runat="server" id="BodyContent" />

... more HTML ...

</body>
</html>

BaseController

public override void OnActionExecuted( ActionExecutedContext filterContext )
{
     if (filterContext != null)
     {
         var context = filterContext.Result as ViewResult;
         if (context != null) {
             context.ViewData["mainMenu"] = 
                 db.MenuData.Where( m => m.Type == "mainMenu" )
                            .Select( m => new Menu {
                                Heading = m.Heading,
                                Items = db.ItemData.Where( i => i.MenuID == m.MenuID )
                                               .OrderBy( i => i.Name )
                                               .Select( i => new MenuItem {
                                                   Text = i.Text,
                                                   Action = i.Operation,
                                                   Controller = i.Table
                                               })
                            });
         }
    }
}
tvanfosson
Any links and/or articles associated with this method?
Brian David Berman
I looked around via google and couldn't find anything that related to RTM. Most of it was older stuff that isn't really applicable to the released version. I'll see if I can add an example.
tvanfosson
Er, I don't know that much about MVC, but is this performant? Does this go to the database with a subquery just to get the menu for each page request?
flipdoubt
@flipdoubt -- it's just an example to show how you could do it. As I said in my answer, my menus are static. If they were derived from the database, you'd probably cache them.
tvanfosson