Tuesday, June 16, 2015

Master Pages in ASP.NET MVC



The Master Pages
<%@ 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>
<%@ 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 Simple Type Templates
<%@ Page Language="C#" MasterPageFile="Template.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ContentPlaceHolderID="Data" runat="server">
<%= Html.Encode(ViewData.TemplateInfo.FormattedModelValue) %>
</asp:Content>
<%@ 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
<%@ 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) %>
<% } %>
<% } %>
<%@ 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) %>
<% } %>
<% } %>

One of the major new features in ASP.NET MVC 2 is templates.
This is a feature that’s similar to Dynamic Data for WebForms. Given an object of a given type, the system can automatically display or edit that object, whether it’s a simple data item (like an integer, a decimal, a string, etc.) or a complex data item (like a class).

To display an item, there are three Display method HTML helpers (each with a few overloads):
The string-based versions can be used to pull things to be displayed from both ViewData and a model (whose exact type you may not know).
The expression-based versions are primarily used for pulling values from the model (they are parametrized by the current model, as shown in the example above). They can also be used for pulling values from some source other than the model or ViewData (for example, with an expression like “model => someOtherValue” which ignores the model entirely). This makes them useful in loops.
The model expressions are simple helpers which operate on the current model. The line DisplayForModel() is equivalent to DisplayFor(model => model).
Let’s start off with a model:
?
and an action:
?
and a view:
?
Running the action yields this result:

As with Html.Display, there are three versions of Html.Editor, which are used to edit objects:
If I change my view from DisplayForModel to EditorForModel, this is what I’ll see:
Now we can see that, for strings and integers, the system will provide text box editors automatically for us.

The template system in MVC 2 contains several built-in templates.
The first one we’re seeing in action here is the complex object template. In order to “display” or “edit” a complex object, the system actually uses reflection to find all the properties on that object, and then automatically generates labels and displays/editors for each of those properties.
If we were to write the core logic for this, it might look something like this:
?
Note that this is nowhere near a complete implementation; I’ll talk about some of what’s missing in a later blog post.
That is the core of the complex object display template: loop over all the properties of the current model, and for each of those properties, show its label and then call Html.Display() to display the actual property value.
What happens when we want to display a string? This is roughly the view code for that:
?
Again, this is not the actual code, but we will see what the real code is in a later blog post.

There is a lot of value in having a bunch of templates built-in, but the bigger value is in being able to override the template at any point in the rendering process.
Here, I’m going to override the display template for strings by creating a partial view named String inside of ~/Views/ControllerName/DisplayTemplates:
And inside this file, I write:
?
Now I refresh the page and see:
You can see that we’ve picked up the new template automatically, and it’s being used for all of our strings. It’s also being used for the integer Age, but we’ll cover why that is in a later blog post.
If we want to write a template for editors, then you place it in a folder namedEditorTemplates.

We have a lot to learn about this new Templates feature in ASP.NET MVC 2. This post has introduced us to the template concept and shown us how we can override the built-in templates. In the next blog post, we’ll talk about what the ModelMetadata class is and how it affects templates, as well as the DataAnnotations attributes that feed into ModelMetadata by default in ASP.NET MVC 2.

One of the classes we introduced with ASP.NET MVC 2 is ModelMetadata. This class is designed to tell you interesting things about the objects you want to display or edit. While we commonly use them when writing templates, this metadata is actually always available, even when you’re not in a template.

When it comes to ModelMetadata, the definition of “model” is probably a bit blurrier than you’re used to.
Let’s say for instance that you have the model from part 1 of this blog series:
You created a strongly-typed view for this model, and if inside the view you access ViewData.ModelMetadata, then the model at this point is the Contact object.
However, through this metadata object, you can also get metadata about all of its properties. This returns a collection of ModelMetadata objects, one for each of the properties. If we were to do this with our Contact object, we’d end up with 3 new metadata objects, one each for FirstName, LastName, and Age. When you’re looking at the model metadata for FirstName, the model type is String (and the container type is Contact). In this way, you can even recursively dive through several layers of complex objects via properties.

Use the one for the current model.
The most common way to get one is to access the ModelMetadata property on ViewData as shown above. This metadata object describes the ViewData’s Model. When you’re rendering a template for an object, this is the most common way to get metadata.
Get one for properties on metadata you already have.
If you have a metadata object in hand, you can call the Properties property, which returns a list of ModelMetadata objects for each of the properties of that model.
Get one from an expression.
The ModelMetadata class has two static methods on it: FromStringExpression and FromLambdaExpression. These are the methods that are used when you want to turn a string expression (like “PropertyName”) or a code-based expression (like “m => m.PropertyName”) into the appropriate ModelMetadata. Most of the existing HTML helpers have been re-written in terms of these two methods, so that they exhibit consistent parsing of expressions (and can use the ModelMetadata to make better decisions about how to display objects).

Before we talk about how ModelMetadata is populated, let’s quickly review what kind of information is available inside of a ModelMetadata object.
Properties about the model and its container
Metadata about the model
Helper methods
The default value for SimpleDisplayText follows these rules:

We’ve added a pluggable metadata provider system in ASP.NET MVC 2. By default, the ModelMetadata objects are constructed with data taken from attributes, primarily from the System.ComponentModel and System.ComponentModel.DataAnnotations namespaces.
When using the default DataAnnotations model metadata provider, the following attributes will influence model metadata:

Now we know a little bit more about the metadata that’s available about your models when writing templates. In the next blog post, we’ll talk about the default templates that are built into ASP.NET MVC 2, and show what they would look like if you wrote them as .ascx files.

Before we talk about the built-in templates, we need to spend a few minutes understanding how template resolution works, so you’ll know how to override the template properly.

Paths
When a template is resolved, the system iterates over several names, looking for a template which matches. For each of the names, it asks the view engines to find a partial view named “DisplayTemplates/TemplateName” or “EditorTemplates/TemplateName”, depending on whether you’ve asked for a display or editor template.
If you’re using the WebForms view engine, that means it searches for display templates in the following locations:
(Replace DisplayTemplates with EditorTemplates for the search paths for editor templates.)
Template Names
The following template names are tried in order:
When searching for the type name, the simple name is used (i.e., Type.Name) without namespace. Also, if the type is Nullable<T>, we search for T (so you’ll get the Boolean template whether you’re using “bool” or “Nullable<bool>”). This means if you’re writing templates for value types, you will need to account for whether the value is nullable or not. You can use the IsNullableValueType property of ModelMetadata to determine if the value is nullable. We’ll see an example of this below with the built-in Boolean template.

One last thing we need to talk about before diving into the template implementations is the TemplateInfo class. The TemplateInfo is available off of ViewData, and unlike model metadata, is only populated when you’re inside of a template.
The primary property you will be using from TemplateInfo is FormattedModelValue. The value of this field is either the properly formatted model value as a string (based on the format strings on ModelMetadata), or the original raw model value (if there is no format string specified).
There are a couple other things we also use (TemplateDepth property and Visited method), which will be explained when we encounter them.

The system has built-in support for 9 display template names: “Boolean”, “Decimal”, “EmailAddress”, “HiddenInput”, “Html”, “Object”, “String”, “Text”, and “Url”. Two of these (“Text” and “String”) have the same implementation. Some of these have counterparts in the editor templates, and some do not.
The default templates in ASP.NET MVC are done in code, but here I’ve replaced their functionality as .ascx files, to illustrate what they do (and give you a starting point for customizing your own versions of all these templates).
DisplayTemplates/String.ascx
There’s nothing really surprising here: we just encode and display the model.
DisplayTemplates/Html.ascx
This is even a little simpler yet, since the “Html” type tells us that the content is HTML and therefore should not be encoded. Be careful when marking your data as “Html” if it comes from end-users, since it opens up the possibility for cross-site scripting (XSS) attacks!
DisplayTemplates/EmailAddress.ascx
This template assumes your model is an e-mail address, and uses it automatically create a mailto: link for it. Note how it uses Model for the e-mail address itself, but FormattedModelValue for the display; that lets you place format strings for display purposes, while still preserving the unedited e-mail address. This pattern is fairly common when the data is both mechanically meaningful (as in the e-mail address) but also displayed.
DisplayTemplates/Url.ascx
As with EmailAddress above, this will interpret your model as a URL and automatically create a link to it.
DisplayTemplates/HiddenInput.ascx
This template is intended to be used with the [HiddenInput] attribute (described in Part 2 of this series). It will generate a display value only if the user explicitly asked for one, by consulting HideSurroundingHtml.
DisplayTemplates/Decimal.ascx
This template displays decimal values with 2 digits of precision by default, since most users will use decimal values to represent currency. Note that it only does this if you haven’t applied a format string (which is the purpose of the check in the if statement).
DisplayTemplates/Boolean.ascx
The Boolean template is an interesting one, because it needs to generate UI that is different for non-nullable booleans vs. nullable booleans. It determines whether or not the model is supposed to be nullable based on IsNullableValueType from ModelMetadata.
The display UI for a non-nullable boolean is a disabled checkbox which is checked or not, depending on the value of the model. The display UI for nullable boolean is a disabled drop-down list with three potential values: “Not Set”, “True”, and "False”.
DisplayTemplates/Object.ascx
It’s worth explaining the logic for this before we look at the code, because the complex object template does a lot of work on your behalf.
The Object template’s primary responsibility is displaying all the properties of a complex object, along with labels for each property. However, it’s also responsible for showing the value of the model’s NullDisplayText if it’s null, and it’s also responsible for ensuring that you only show one level of properties (also known as a “shallow dive” of an object). In the next blog post, we’ll talk about ways to customize this template, including performing “deep dive” operations.
Let’s take a look at the source here, line by line, to understand which bit is doing what.
This says we only want to print the model’s NullDisplayText if the model is null.
This limits us to a single level of complex object ("shallow dive"). The TemplateInfo class tracks the depth of templates that you've shown automatically, and the TemplateDepth for the top level template will be 1.
This is the main loop when we're showing the objects properties. We filter the property list to remove anything where the user has said "do not display this". The other filter asks TemplateInfo if we've already rendered this object before; this helps us prevent infinite recursion when doing "deep dive" templates that might come from objects with circular references (like a parent/child relationship where both objects have pointers to one another).
If the user has asked to hide the surrounding HTML, then all we want to do is display the property by itself. We don't want any of the "extra" stuff around it, like labels.
This will show the display name of the property, surrounded by a div tag, if the display name is not null or empty. Since the display name is not empty by default (it will be the name of the property), that means the user must request for the display name to be hidden by explicitly setting it empty (using [DisplayName] if you're using the default DataAnnotations metadata provider).

The editor templates will be slightly more complicated than the display templates, since they include the ability to edit the values. They are built upon the existing HTML helpers. There are 7 built-in editor templates: “Boolean”, “Decimal”, “HiddenInput”, “MultilineText”, “Object”, “Password”, and “String”.
One thing you’re going to see that’s unusual here is that we will often by passing empty string as the name to the HTML helpers. Normally this isn’t legal, but in the case of templates, we keep a “context” which tracks where we are in the name of things. This is helpful with complex objects inside of complex objects, because we want our names to indicate where we are in the hierarchy of things (f.e., “Contact.HomeAddress.City” vs. “Contact.WorkAddress.City”).
When you pass a name to the HTML helpers, you’re saying things like “give me a textbox to edit the property of this object named ‘City’.” But what happens when your template isn’t for the address (complex object), but instead for the city (a simple string)? Passing an empty string to the HTML helper says “give me a textbox to edit myself.”
EditorTemplates/String.ascx
Again we start with String, because it’s the simplest template to understand. This tells the system we want a text box (to edit myself), and we want it populated initially with the value of the formatted model value. In addition, we want to attach two CSS classes to the text box, “text-box” and “single-line”. With many of the CSS classes that we use here, you will find that we have provided default styles in MVC 2 that make them look slightly better out of the box.
EditorTemplates/Password.ascx
The Password template is similar to String, except that it calls Html.Password and it adds a third CSS class ("password") to the rendered control.
EditorTemplates/MultilineText.ascx
Again, no big surprises here. We call TextArea, and we pass row and column size as 0 (since we style the text area with CSS), and use the CSS class "multi-line" instead of "single-line".
EditorTemplates/HiddenInput.ascx
This is a lot more complex than the display version, because it has to do a lot more. The ModelValue property determines if the model is a LINQ to SQL Binary object or a byte array, and converts the value into a Base64 encoded value if so; otherwise, the raw model value is placed into the hidden input.
In addition to rendering the hidden input, it also needs to know if it should generate a display of the value.
EditorTemplates/Decimal.ascx
The Decimal editor template is nearly identical to the display template version, except that it ends up generating a text box for editing the value.
EditorTemplates/Boolean.ascx
The Boolean editor template is similar to the display template, except that it uses the built-in HTML helpers for DropDownList and CheckBox.
EditorTemplates/Object.ascx
The Editor template for Object is nearly identical to the Display template, except that now we’ve added a call to ValidationMessage so that our default complex object editor will show error message asterisks.

In Part 3, we saw what the default templates would look like if we’d written them as .ascx files. In this blog post, we’ll discuss some of the customizations you can make to the Object templates to enable different features and different displays for your template-based UI.
For these examples, here are the models, controller, and views that we’ll be using.
Models/SampleModel.cs
Models/ChildModel.cs
Controllers/HomeController.cs
Views/Home/Index.aspx
Views/Home/Edit.aspx

When we show this home controller without any customizations, this is what the details page looks like:
Default Display
And this is our edit page:
Default Editor

One of the more commonly requested layouts is to do a tabular layout inside of the linear name/value, name/value layout that we do by default. Notice that the editor version of this layout also adds asterisks to the label for required fields.
Views/Shared/DisplayTemplates/Object.ascx
Which creates this layout:
Tabular Display
Views/Shared/EditorTemplates/Object.ascx
Which creates this layout:
Tabular Editor

In the screenshots above, ChildModel is showing as “(null value)”. ChildModel is itself a complex model, so it follows the rules for shallow dive vs. deep dive. Before we have a child model object, it’s showing the NullDisplayText as we set in the attribute in the model above.
Notice that even in edit mode above, we can’t edit the child model. That’s because the shallow dive logic prevents us from presenting a recursive editing UI.
If we change the Editor template above and remove the first “if” statement (which is what prevents the deep dive), then the editor will now show us editing fields for the child model:
Tabular Editor with Deep Dive enabled
And now our display shows:
Tabular Display with child model (shallow dive)
Since we haven’t changed our Object Display template, we still get a shallow dive on this object. Further, it’s showing us the full name because we’ve used the DataAnnotations [DisplayColumn] attribute to say “display this property when showing this complex object in shallow form”. We’ve pointed [DisplayColumn] to a synthesized property called FullName, which we don’t normally show because we’ve annotated it with [ScaffoldColumn(false)].
If we change the Object Display template to do a deep dive, then we would see this:
Tabular Display with Deep Dive

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 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
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
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 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
EditorTemplates/String.aspx

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
EditorTemplates/Object.aspx
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.

No comments:

Post a Comment