Archive for the ‘ASP.NET MVC’ tag
ASP.NET MVC views and content_for
I’ve switched over development at work from ASP.NET WebForms to the new ASP.NET MVC framework, and so far I’m loving it. It codifies several practices I was already using within WebForms development, while making it easier to code cleaner in other aspects. The MVC framework certainly presents some difficulties, especially coming from being entrenched in WebForms for so long. Sometimes simple things.
For instance, what is the best way to set the HTML page title for a view that uses a master page? Almost all of the examples provided for ASP.NET MVC pass the page title in from the controller using the ViewData dictionary.
ActionResult Index()
{
// ... action code here
ViewData["PageTitle"] = "Hello World!";
return View();
}
<head>
<title><%= ViewData["PageTitle"] %></title>
</head>
While this approach certainly works, it relies on both the weakly-typed ViewData dictionary and magic literals to retrieve the title. I want to pass in strongly-typed models for all my views.
None of the options for using strongly-typed models containing the page title (and other HTML page data) seemed very appealing. It would mean either passing the model within a “Page” container, or deriving from a common interface that contains the page content. Wrapping the model is cumbersome when working within the view, and putting page data inside the model objects simply is not an option
// Awkward but usable model wrapper.
public class PageData<ModelType>
{
public string Title { get; set; }
public ModelType Model { get; set; }
}
// An even worse idea - why should the model care about this?
public interface IHasPageData
{
string PageTitle { get; set; }
}
class MyModel : IHasPageData { }
Indeed, unless there is significant calculation involved — and why would there be for a page title — why should the controller determine the title for the HTML view at all? Keeping in the spirit of RESTfullness, a “page title” is meaningless for XML or JSON results. Therefore, the HTML view itself should be responsible for its own title.
This is easy for normal views; you have full control of the HTML and full access to the model. However, things become more complicated when you use a master page view. You somehow need to pass up the title of your current view to the master page view. Moreover, you need to do this at run time if you intend to use your strongly-typed model within your title.
Look to the leader as always. After a bit of searching, I found the construct that Ruby on Rails uses to do this: content_for. You would use content_for :title "Hello World" in your view to set the title, then yield :title to output it within your master layout. Bingo - just set up a similar mechanism
There’s an ever so slight problem - the master page view is run before the page view. This unfortunately means that the contents must be registered within the page view’s code behind. It’s a price to pay, but I think it may be worth it to separate HTML-specific view elements from the controller.
So I derived a couple of classes - ContentViewPage (and its generic equivalent) and ContentViewMasterPage. They add a few methods: ContentFor<ContentKey> which associates a content key with either a string or a rendering action, and YieldContentFor<ContentKey> which outputs the contents associated with the content key. Rather than using constants to define the keys, I chose to use types as the key. While I fully admit it’s an abuse of generics, it provides more unique keys and I like the readability it provides.
Registering content is fairly simple to do:
protected override void RegisterContents(object model)
{
ContentFor<PageTitle>("Hello World");
}
Then, when you want to render that piece of content, you simply use the YieldContentFor<ContentKey> method.
<head>
<title><% YieldContentFor<PageTitle>(); %></title>
</head>
This example is pretty trivial. Real world scenarios would involve constructing the title from the model object, passing in renderings for navigation menus for the master view, including specialized stylesheets or scripts for the view, and so on. The (extremely simple) implementation is left as an exercise for the reader.