Series Index
- Part 1: Introduction
- Part 2: ModelMetadata
- Part 3: Default Templates
- Part 4: Custom Object Templates
- Part 5: Master Page Templates
Opinionated Input Builders
During the last TechEd, Eric Hexter talked with David Fowler from the ASP.NET team about the previous examples we’d released showing Dynamic Data-like functionality for MVC applications. After that talk, Eric ran a multi-part blog post series titled Opinionated Input Builders where he showed functionality that was similar to the Templates feature that we’d been working on for MVC 2.
One of my design goals when I did our Templates feature was to make sure we enabled the kinds of scenarios that Eric used in his example. The primary difference is that he relies on Master Pages to do the actual layout of the items. This means that you can always just call DisplayXxx or EditorXxx, and whether you have a simple or a complex object, you get the complete “chrome” included: labels, inputs, and validation messages.
This blog post shows how to use Eric’s technique with the MVC 2 Templates feature. For demonstration purposes, I'm going to use a table-style layout like he did (and like I showed in Part 4 of this series), but this could just as easily use a linear display like the default templates use.
The Master Pages
The first thing we need to define is the master pages that will be used by the templates. There is one each for Display and Editor templates. These master pages will be used by the individual item templates to display one item from the model.
DisplayTemplates/Template.master
<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<script runat="server">
protected override void OnInit(EventArgs e) {
base.OnInit(e);
if (ViewData.ModelMetadata.HideSurroundingHtml) {
TablePlaceholder.Visible = false;
}
else {
Controls.Remove(Data);
DataPlaceholder.Controls.Add(Data);
}
}
</script>
<asp:ContentPlaceHolder runat="server" id="Data" />
<asp:PlaceHolder runat="server" id="TablePlaceholder">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
<td style="width: 10em;">
<div class="display-label" style="text-align: right;">
<asp:ContentPlaceHolder runat="server" id="Label">
<%= ViewData.ModelMetadata.GetDisplayName() %>
</asp:ContentPlaceHolder>
</div>
</td>
<td>
<div class="display-field">
<asp:PlaceHolder runat="server" id="DataPlaceholder" />
</div>
</td>
</tr>
</table>
</asp:PlaceHolder>
The core behavior of this master page is that it defines two content placeholders named “Label” and “Data”. The “Label” placeholder has default content which shows the display name of the property in question, which is usually the right answer for labels (but by making it a placeholder, then each individual template can make that decision for themselves).
This template uses a little WebForms magic during OnInit to rearrange the page depending on whether you’re interested in showing the surrounding HTML or not. If you want to hide the surrounding HTML, then we hide the table and leave the data placeholder outside where it gets displayed on it own; if you want to preserve the surrounding HTML, then we move the data placeholder into its proper place in the table.
EditorTemplates/Template.master
<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<script runat="server">
protected override void OnInit(EventArgs e) {
base.OnInit(e);
if (ViewData.ModelMetadata.HideSurroundingHtml) {
TablePlaceholder.Visible = false;
}
else {
Controls.Remove(Data);
DataPlaceholder.Controls.Add(Data);
}
}
</script>
<asp:ContentPlaceHolder runat="server" id="Data" />
<asp:PlaceHolder runat="server" id="TablePlaceholder">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
<td style="width: 10em;">
<asp:ContentPlaceHolder runat="server" id="Label">
<div class="editor-label" style="text-align: right;">
<%= ViewData.ModelMetadata.IsRequired ? "*" : "" %>
<%= Html.Label("") %>
</div>
</asp:ContentPlaceHolder>
</td>
<td>
<div class="editor-field">
<asp:PlaceHolder runat="server" id="DataPlaceholder" />
<asp:ContentPlaceHolder runat="server" ID="Validation">
<%= Html.ValidationMessage("", "*") %>
</asp:ContentPlaceHolder>
</div>
</td>
</tr>
</table>
</asp:PlaceHolder>
The editor version of this template is nearly identical, except that now we’ve introduced a third content placeholder named “Validation”, with default content that calls Html.ValidationMessage(). We’ve also added asterisks for required fields, just like we did with our previous tabular template.
The Simple Type Templates
The simple type templates will look nearly identical to the previous versions we’ve seen, except that they will be wrapped into <asp:Content> blocks and reference the master page. For demonstration purposes, I’ll show the Display and Editor templates for String, and leave the porting of the rest of the templates as an exercise for the reader.
DisplayTemplates/String.aspx
<%@ Page Language="C#" MasterPageFile="Template.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ContentPlaceHolderID="Data" runat="server">
<%= Html.Encode(ViewData.TemplateInfo.FormattedModelValue) %>
</asp:Content>
EditorTemplates/String.aspx
<%@ Page Language="C#" MasterPageFile="Template.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ContentPlaceHolderID="Data" runat="server">
<%= Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue,
new { @class = "text-box single-line" }) %>
</asp:Content>
The Object Templates
When we examine the Object templates, we’ll see that they are significantly simpler than the older versions, because there is no actual HTML generation going on in here; that’s left to the master pages as appropriate. We’re just left with our shallow dive logic and the property loop.
DisplayTemplates/Object.aspx
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<% if (ViewData.TemplateInfo.TemplateDepth > 1) { %>
<%= ViewData.ModelMetadata.SimpleDisplayText %>
<% } else { %>
<% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm))) { %>
<%= Html.Display(prop.PropertyName) %>
<% } %>
<% } %>
EditorTemplates/Object.aspx
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<% if (ViewData.TemplateInfo.TemplateDepth > 1) { %>
<%= ViewData.ModelMetadata.SimpleDisplayText %>
<% } else { %>
<% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm))) { %>
<%= Html.Editor(prop.PropertyName) %>
<% } %>
<% } %>
The only difference between the Editor and Display templates is the call to Html.Editor vs. Html.Display.
Also notice that these templates don’t use the master page. That’s because they’re not generating any UI of their own, and they rely on the individual items to use the master page to get their own surrounding UI.
Wrapping Up
After we apply all these changes, the resulting display is identical to the tabular display we had in the previous blog post, except that now we’ve centralized all the layout decisions into the master pages. This would allow us, for instance, to change layout at runtime just by dynamically changing the master page each template referenced; it’s also a nice separation of responsibilities because the layout is now separated from the shallow vs. deep dive and property iteration decisions.
I think this will probably be the last blog post in this series, unless there is some feedback about anything people would like covered that hasn’t been shown yet. I hope they’ve helped you understand the new Templates feature in ASP.NET MVC 2.
Happy Web Developing!