ASP.Net MVC Database-driven menu with caching

Saajid Ismail picture Saajid Ismail · Dec 28, 2009 · Viewed 7.2k times · Source

I am trying to create a menu for my website. It needs to meet the following requirements

  • it must be database driven, pulling data from DB to build up the menu structure
  • the data being pulled from the DB needs to be cached - I do not want to hit the DB for each page request

At the moment, I have a simplistic example running, but I don't know how to integrate caching. I think I might have to rework the entire way I do this. Here it is:

I have a ProductMenuAttribute, which pulls the data from the DB, and stores it in the ViewData:

public class ProductMenuAttribute: FilterAttribute, IActionFilter
{
    #region IActionFilter Members

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext != null)
        {
            var context = filterContext.Result as ViewResult;
            if (context != null)
            {
                ProductsRepository repository = new ProductsRepository(Properties.Settings.Default.SqlConnectionString);

                context.ViewData.Add("ProductsList", repository.GetAllProductRanges());
            }
        }
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {

    }

    #endregion
}

In my Site.master I pull the data from the ViewData and use it to render my menu. This is a small snippet in my un-ordered menu list, which gets styled using CSS. Here is the code:

            <li>
                <%= Html.ActionLink("Products", "Index", "Products")%>

                <%  IQueryable<ProductRange> productRanges = ViewData["ProductsList"] as IQueryable<ProductRange>; %>

                    <% if (productRanges != null)
                       { %>

                    <ul>
                        <% foreach (ProductRange range in productRanges) 
                           { %>   
                            <li><%= Html.ActionLink(range.Name, "RangeDetails", "Products", new { id = range.ID }, null)%></li>
                        <% } %>
                    </ul>

                    <% } %>
            </li>

I then decorate each controller with the [ProductMenu] attribuate as follows:

[ProductMenu]
public class HomeController : BaseController
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult About()
    {
        return View();
    }
}

Now whenever any of the actions on my controller are hit, the OnActionExecuted method in the ProductMenuAttribute class will be called, which will set the ViewData, which will eventually be used on my Site.Master to build up my menu from the DB, which time I call any of the actions.

The problem now is - how do I add caching to this scenario?? I have no clue where to start, and have a feeling the solution I have is not cacheable.

Answer

Saajid Ismail picture Saajid Ismail · Feb 9, 2010

I think what I am really looking for is to use the Html.RenderAction() helper extension from the MVC Futures project.

The idea is to use the above to call an action on a controller, which will generate the HTML menu structure by pulling data from the DB. I then also use simple Output Caching to cache the data for a few minutes..

This is the simplest approach I've found so far to achieve what I want.

EDIT: Phil Haack blogged about this recently - Html.RenderAction and Html.Action. A great blog post covering all my exact needs, with explanations of all the issues.

To get the caching to work correctly, I would need to put my Html.RenderAction() call inside a ViewUserControl with the OutputCaching directive set as follows:

<@ OutputCache Duration="100" VaryByParam="None" %>

I then call the above with Html.RenderPartial(), and voila, it all works!