About a year ago, I wrote an article for DevProConnections exploring ways to inject global data into ASP.NET MVC views (see "Dealing with Data Shared Among Views" The solutions that I presented are, for the
most part, common tricks. Recently, however, I needed to enhance those solutions to better address a problem in a larger application that had several
master/detail views.
This problem isn’t unique to master/detail views but shows up clearly when you deal with them. When you write a master/detail view in Web Forms, you
have only one concern: how to get key information for the item that is selected in the master. When you know how to do that, you can populate the
detail view and render the page; you don't need to worry about refilling the master view. That's one benefit of the viewstate.
In ASP.NET MVC, you have no viewstate. In this context, the news isn’t great because it means that you need to be concerned with filling the detail
view and refilling the master view. The issue isn’t really a coding problem; it's purely a design issue. But unresolved design issues never die—they
come back unexpectedly as the size of the project grows.
The Design Issue
In ASP.NET MVC, each request results a controller conducting an action or, better yet, triggering a service component. The action relates to a specific
user request, such as "show me the list of countries where the company has subsidiaries" or "show me details about this country." Ideally, you want to
serve the former request by running code that gets only the list of countries, and serve the latter request by running code that gets only details
about a given country. The issue is that ASP.NET MVC forces the code that serves the second request to also provide the list of countries (again).
Otherwise, the drop-down list of countries will be empty when the detail view is shown (see Figure 1).

Figure 1: Improperly filled master view
Figure 2 shows a sample controller class for the application that Figure 1 shows. The controller uses injected worker components to serve actions and
get view model objects. (This approach is an emerging pattern for ASP.NET MVC applications, and I recommend it to everybody, but not for just any
application. The approach is described thoroughly in Programming ASP.NET MVC 3, Microsoft Press, 2011.)
The worker service class does the job of populating view model objects, which the controller then hands to the view engine. Figure 3 shows a sample
worker service. The first method in the listing returns a fully initialized view model object, which has the page title and a list of countries. The
second method returns a view model object that sets the title and country details, but does not list countries.
Admittedly, you could just add an extra call to GetCountryViewModel to retrieve and store the list of countries. You wouldn’t experience the failure
that is shown in Figure 1 and could leave the office early. But what if the amount of information that the multiple views share is large and comes from
scattered sources? You would need that information in a variety of controllers (or worker services) for a variety of views. How many dependencies
should you be passing around?
The idea is to leave the controller (or worker service) in charge of only the data that must be manipulated in that specific call context, and to move
the remaining code elsewhere. An excellent place to move this extra code is to an action filter.
The ViewModelFiller Action Filter
Action filters are small chunks of behavior that you can attach to controller methods. In a way, an action filter is like an interceptor and can insert
your code in four places—before and after the action is executed and before and after the view result is executed. For our purposes, we need an action
filter that kicks in right after the action methods returns. Here's the skeleton of such a filter:
public class ViewModelFillerAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var model = filterContext.Controller.ViewData.Model;
// Apply changes to the view model object
...
}
}