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
2009-04-07 02:48:36
Any links and/or articles associated with this method?
Brian David Berman
2009-04-07 02:55:59
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
2009-04-07 03:11:01
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
2009-04-07 03:41:57
@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
2009-04-07 05:16:22