My usage of ASP.NET has typically been in the Web Forms category, but I’ve been teaching myself ASP.NET MVC recently and have a few projects in the works that utilize it. Overall, I really like what MVC has to offer. Having complete control of the rendered HTML markup alone is worth the extra effort of learning the architecture and patterns. I did, however, run up against a big problem in a current project.

The Problem

It’s very common to have a sidebar section in a website that contains any number of widgets. This blog is a perfect example. The main content is the posts themselves, and along the side is a configurable group of sections with extra information, in this case “Recent Posts”, “Archives”, etc. In ASP.NET MVC, these widgets will usually be rendered as PartialViews. But what happens when the PartialViews display information that isn’t directly related to the main content? In the ASP.NET MVC paradigm the Controller handles requests by communicating with the Model to obtain the necessary data and then passing that data to the appropriate View as ViewData. The View can then iterate over the ViewData.Model property to display the data. Any PartialView widgets that need data other than that in ViewData.Model are stuck.

A few options came to mind to deal with this:

  1. Make the PartialViews responsible for gathering their own data. This has several drawbacks. Views and PartialViews are supposed to be responsible for displaying information only and are to be kept as lightweight as possible. Also, introducing data access logic into a PartialView goes against the separation of concern principle that is at the heart of the MVC pattern. Even worse, this could result in the same data being retrieved more than once when the PartialView is related to the parent ViewData.
  2. All Controller actions must generate complete ViewData for the main View and all PartialViews. This keeps the gathering of ViewData inside the Controllers (via the Model) where it belongs, but it has issues as well. If the widgets displayed are dynamically determined at runtime from a config file (as they are in my case), the action method, and likely also the Views, will quickly become a mess of if/then or switch/case blocks to make sure all the PartialViews are accounted for. Hardly ideal.

One Solution

I did some searching and came across a post on Steve Sanderson’s blog that details a decent solution. From the same article it looks like the MVC Futures project is looking at potential solutions for this problem as well.

I ended up going with a modified version of Steve’s solution. Instead of using his PartialRequest class, I created a SidebarHelper.cs class and populated it with methods to construct and return a ViewDataDictionary for any PartialView widgets I care to create. A typical implementation in this class looks like this:

public static ViewDataDictionary ScheduleData()
{
    DataClassesDataContext dc = new DataClassesDataContext();
    ViewDataDictionary viewData = new ViewDataDictionary();
    IEnumerable<ScheduleItem> shows;

    try
    {
        shows = dc.Schedules.UpcomingShows();
        viewData.Model = shows;
    }
    catch (Exception ex)
    {
        // error handling omitted for brevity
    }

    return viewData;
}

Then in the Controller action method for any View that needs to display the Schedule widget, I can add this line:

ViewData["scheduleData"] = SidebarHelper.ScheduleData();

…and in the corresponding View this line passes the dictionary to the PartialView for display (line breaks added here to avoid wrapping):

<% Html.RenderPartial(
    "SidebarScheduleControl",
    (ViewDataDictionary)ViewData["scheduleData"]
); %>

The great thing about this is that when the PartialView does use the same ViewData as the parent View, or even just a subset of it, there’s no need to fetch the data a second time. In those cases I just use a different overload of the Html.RenderPartial(...) method and pass in the already existing ViewData.Model.

Other Solutions?

Although this ended up working out, I’m not entirely convinced of its robustness or that it closely follows best practices. As I mentioned at the beginning, I’m still relatively new to the MVC way of doing things. I’ll continue using it for now, while waiting to see what, if any, solutions to this problem get merged from Futures into the next version of ASP.NET MVC.