MVC 2 is official, I've been using it for a while now (since preview), my favorite feature by far is templating. I've been using the templating feature extensively to upgrade an existing production application, in particular some quite ugly views.
Before templates to create a reusable UI, you really only had the options of user controls or writing your own Html helpers, the former will almost certainly mean a compromise. I believe the templating feature will assist in the goals of separation of concerns and being DRY.
Contents
Who should read this post
This post is aimed at people who have experience of MVC, but want to find out more about the templating feature of MVC 2. You will need to understand strongly typed Html helpers (passing your view data property to a Html helper as Lambda expression). If you don't know what strongly typed Html helpers are I recommend you read Scott Gu's excellent post first.The Basics
Download the code for this exampleConsistent with other strongly typed Html helpers Html.TextBox(model=>model.foo) etc, the syntax for loading custom templates is exactly the same as any other Html helper.
Like views the folder structure can be used to determine what template should be used. This allows you to use the built-in templates first, then when required customise using the same syntax. Templates come in two flavors Editor and Display.
So how easy it is to use templates?
We are going to create a very simple MVC application that posts a Employee Model and then displays the model the user submitted. We are going to use templates to reduce the amount of mark-up in the two views.
Let's start by creating a new MVC ASP.NET Web Application (File -> Project -> Web -> MVC ASP.NET Web Application).
Then lets code a very simple model representing an Employee:
public class EmployeeModel {
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? DateOfBirth { get; set; }
}
The simplest of Controllers:
[HandleError]
public class EmployeeController : Controller {
[HttpGet]
public ActionResult Index() {
return View();
}
}
And lastly lets add view for the Employee\Index action, this will be a strongly typed View for a "Create" scenario.
The quickest and easiest way to do this is by creating a "Employee" folder underneath /views, then add a view by right mouse clicking on the "Employee" folder and selecting "Add View", when presented with the "Add View" dialog, do the following:
- Name the view "Index"
- Check "Create a strongly-typed view"
- Select the Employee model as the view data class
- Select "Create" as your View content
This will generate the View below:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcTempDemo1.Models.EmployeeModel>" %>;
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Index</h2>
<% using (Html.BeginForm()) {%>;
<%= Html.ValidationSummary(true) %>;
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%= Html.LabelFor(model => model.FirstName) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.FirstName) %>
<%= Html.ValidationMessageFor(model => model.FirstName) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.LastName) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.LastName) %>
<%= Html.ValidationMessageFor(model => model.LastName) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.DateOfBirth) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.DateOfBirth) %>
<%= Html.ValidationMessageFor(model => model.DateOfBirth) %>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<div>
<%= Html.ActionLink("Back to List", "Index") %>
</div>
</asp:Content>
Debugging /Employee/Index you should see the following:
To use the built-in templates just replace the Html.TextBoxFor helper with the Html.EditorFor helper, the mark-up will look like this:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcTempDemo1.Models.EmployeeModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Index</h2>
<% using (Html.BeginForm()) {%>
<%= Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%= Html.LabelFor(model => model.FirstName) %>
</div>
<div class="editor-field">
<%= Html.DisplayFor(model => model.FirstName) %>
<%= Html.ValidationMessageFor(model => model.FirstName) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.LastName) %>
</div>
<div class="editor-field">
<%= Html.DisplayFor(model => model.LastName) %>
<%= Html.ValidationMessageFor(model => model.LastName) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.DateOfBirth) %>
</div>
<div class="editor-field">
<%= Html.DisplayFor(model => model.DateOfBirth) %>
<%= Html.ValidationMessageFor(model => model.DateOfBirth) %>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<div>
<%= Html.ActionLink("Back to List", "Index") %>
</div>
</asp:Content>
Now debugging /Employee/Index you will notice that the view looks exactly the same, however for each of the view data's property types you are now calling the default Editor template, it just so happens for DateTime and String it's a Textbox. Next we'll add our own template.
Adding Your Own Editor Template
Download the code for this exampleFor editing view data properties that have the type of string, we are going to add our own Shared Editor template.
Adding your own editor template is simple, create a folder underneath /Views/Shared called EditorTemplates, then add a view by right mouse clicking on the EditorTemplates folder and selecting "Add View", when presented with the "Add View" dialog, do the following:
- Name the template "string"
- Check "Create a partial view"
- Check "Create a strongly-typed view"
- Type in "string" as the view data class
Paste the following code into your template:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<string>" %>
<div class="editor-label">
<%= Html.LabelFor(model => model) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model) %>
<%= Html.ValidationMessageFor(model => model) %>
</div>
As you can see, we have refactored our view into a template, so that the Label, Textbox and Validation Message are all part of the one template. To make the passed in view data strongly-typed, you will notice the familiar use of ViewUserControl<string>. View data passed to a strongly-typed template is the model for that template (a sort of mini-model), therefore you can work with it like any other model. The other piece of the jigsaw is TemplateInfo, but I will discuss that later on in the post.
Now we need to remove the redundant markup from the view, change the /Views/Employee/Index view as follows:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcTempDemo1.Models.EmployeeModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Index</h2>
<% using (Html.BeginForm("Details","Employee")) {%>
<%= Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<%= Html.EditorFor(model => model.FirstName) %>
<%= Html.EditorFor(model => model.LastName) %>
<%= Html.EditorFor(model => model.DateOfBirth) %>
<p><input type="submit" value="Create" /></p>
</fieldset>
<% } %>
<div><%= Html.ActionLink("Back to List", "Index") %></div>
</asp:Content>
As you can see we followed the DRY principle (Don't repeat yourself) with our view and as a result reduced the amount of code by about 50%.
Debugging /Employee/Index you will notice that the view looks exactly the same (sorry re-occurring theme in this post), but now you are calling your own Editor template for any of your view data properties that have a type of string.
How does this work?
The only significant difference between views and templates is that rather than naming the template after a action, we name it after the simple name of the target type. The MVC framework takes care of searching for matching templates. When calling the Editor template in our example Html.EditorFor(model => model.FirstName), the view data passed to the template is the type of and value of the model property selected by the Lambda expression.As previously mentioned templates work in a similar way to views, the folder structure is used to determine what template should be used.
Therefore, if the template is controller specific you create a folder called EditorTemplates underneath Views/ControllerName (in our example Views/Employees) or under "Shared", if you want the template available to all views.
When a view calls a template it will look in Views/ControllerName/EditorTemplates first then /Shared/EditorTemplates.
As seen in our first example if the template isn't found rather than throwing an error, it will just default to the template for that type.
Limitations?
A current limitation around templates is generic types, for example if our Employee model had a property called Offices that had a type of List<Office>, because the MVC framework uses the type's simple name to select your template you would have call it it List`1.ascx, not ideal as the template will be selected for any List<T>, there are some good workarounds for this limitation that I will discuss later on in the post.Display Templates
Download the code for this exampleI haven't really mentioned Display Templates, Display Templates work in exactly the same way as Editor Templates, except they're intended for displaying view data π
For reference, below is how to create a Display Template, but the steps are pretty much the same, so I wouldn't be offended if you skipped this section.
First lets add the following action to our Employee controller:
[HttpPost]
public ActionResult Details(EmployeeModel model)
{
return View(model);
}
Then lets add a view for the Employee\Details action this will be a strongly-typed view for a "Details" scenario.
The quickest and easiest way to do this is by right mouse clicking on the "Employee" folder and selecting "Add View", when presented with the "Add View" dialog, do the following:
- Name the view "Details"
- Check "Create a strongly-typed view"
- Select the Employee model as the view data class
- Select "Details" as your View content
This will generate the View below:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcTempDemo1.Models.EmployeeModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Details
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Details</h2>
<fieldset>
<legend>Fields</legend>
<div class="display-label">FirstName</div>
<div class="display-field"><%= Html.Encode(Model.FirstName) %></div>
<div class="display-label">LastName</div>
<div class="display-field"><%= Html.Encode(Model.LastName) %></div>
<div class="display-label">DateOfBirth</div>
<div class="display-field"><%= Html.Encode(String.Format("{0:g}", Model.DateOfBirth)) %></div>
</fieldset>
<p>
<%= Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> |
<%= Html.ActionLink("Back to List", "Index") %>
</p>
</asp:content>
Now we need to change the Index view, so it posts to our new Details action:
using (Html.BeginForm())
To:
using (Html.BeginForm("Details","Employee"))
Debug \Employee\Index fill out the form and click on the create button, you should see something similar to:
Let's change the Details view to use the built-in templates by replacing Html.Encode helper with Html.DisplayFor helper, the mark-up will look like this:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcTempDemo1.Models.EmployeeModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Details
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Details</h2>
<fieldset>
<legend>Fields</legend>
<div class="display-label">FirstName</div>
<div class="display-field"><%= Html.DisplayFor(model => model.FirstName) %></div>
<div class="display-label">LastName</div>
<div class="display-field"><%= Html.DisplayFor(model => mModel.LastName) %></div>
<div class="display-label">DateOfBirth</div>
<div class="display-field"><%= Html.DisplayFor(model => model.DateOfBirth)) %></div>
</fieldset>
<p>
<%= Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> |
<%= Html.ActionLink("Back to List", "Index") %>
</p>
If you debug \Employee\Index and submit the form again, just like the EditorTemplate example, you will notice that the view looks exactly the same, however you are now calling the default DisplayTemplate for each of the types.
Adding Your Own Display Template
We now have a requirement to make each of labels for the Details view bold, we are also going to use the opportunity to refactor the view. To do this we are going to create a Shared template for handling properties that are type of string.
To add a display template create a folder underneath /Views/Shared called DisplayTemplates, then add a view by right mouse clicking on the folder and selecting "Add View", when presented with the Add View dialog, do the following:
- Name the template "string"
- Check "Create a partial view"
- Check "Create a strongly-typed view"
- Type in "string" as the view data class
Paste the following code into your template:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<string>" %>
<div class="display-label"><strong><%=Html.LabelFor(model => model)%></strong></div>
<div class="display-field"><%=Html.Encode(Model)%></div>
We have another requirement to show the Date of Birth in long format, so let's create a Shared template for displaying DateTime view data properties.
Add the template in the same way you did for the string template, except use DateTime for the View name and View data class.
Paste the following code into your template:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DateTime?>"%>
<div class="display-label"><strong><%=Html.LabelFor(model => model)%></strong></div>
<div class="display-field"><%=Html.Encode(Model.HasValue ? Model.Value.ToLongDateString() : string.Empty)%></div>
Remove the redundant markup from the view, change the /Views/Employee/Details view as follows:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcTempDemo1.Models.EmployeeModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Details
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Details</h2>
<fieldset>
<legend>Fields</legend>
<%=Html.DisplayFor(model => model.FirstName)%>
<%=Html.DisplayFor(model => model.LastName)%>
<%=Html.DisplayFor(model => model.DateOfBirth)%>
</fieldset>
<p>
<%= Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> |
<%= Html.ActionLink("Back to List", "Index") %>
</p>
</asp:Content>
Debug\Employee\Index, fill out the form and click on the create button, you can now see that our view includes our new requirements for the DateTime and string types.
In the same way we did for the Index view, using templates we stuck to the DRY principle and reduced the code in the Details view significantly. Our Display templates work in exactly the same way as our Editor template, except we created the Display templates in the DisplayTemplates folder.
More Useful Example β DateTime Editor Template Recipe
Download the code for this exampleI thought I'll post a more useful example that you can use in your own projects. In this example we have a requirement to make it easier for the user to select a date. To meet this requirement we are going to create a date picker using an editor template and jQuery UI.
First of all you need to download jQuery UI, take all the defaults, but select the Redmond theme (you could of course select any theme, just change the code in the master page accordingly).
Once downloaded, copy the \development-bundle\ui folder to the Scripts folder in your project. In your project under \content create a folder called jqueryui, then copy the \css\redmond folder to the \content\jqueryui folder. Ensure that the folders you copied are included in your project.
Change the head of the \Views\Shared\Site.Master master page to reference the jQuery UI scripts and theme CSS, the mark-up will look like this:
<head runat="server">
<title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
<script type="text/javascript" src="../../scripts/jquery-1.4.1.min.js"></script>
<script src="../../Scripts/ui/minified/jquery.ui.core.min.js" type="text/javascript"></script>
<script src="../../Scripts/ui/minified/jquery.ui.datepicker.min.js" type="text/javascript"></script>
<link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
<link href="../../Content/jquery-ui/redmond/jquery-ui-1.8.custom.css" rel="stylesheet" type="text/css" />
</head>
Next lets add our own Shared Editor template, for editing view data properties that are type of DateTime.
Add a view by right mouse clicking on the Views\Shared\Editor template folder and select "Add View", when presented with the "Add View" dialog, do the following:
- Name the template "DateTime"
- Check "Create a partial view"
- Check "Create a strongly-typed view"
- Type in "DateTime" as the view data class
Paste the following code into your template:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DateTime?>" %>
<%string name = ViewData.TemplateInfo.HtmlFieldPrefix;%>
<%string id = name.Replace(".", "_");%>
<div class="editor-label">
<%= Html.LabelFor(model => model) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model) %>
<%= Html.ValidationMessageFor(model => model) %>
</div>
<script type="text/javascript">
$(document).ready(function() {
$("#<%=id%>").datepicker({
showOn: 'both',
dateFormat: 'dd M yy',
changeMonth: true,
changeYear: true
});
});
</script>
To see the date picker in action, debug\Employee\Index and click the "..." button next to the DateOfBirth textbox.
This shared template is a completely reusable and will work for any view data that is type of DateTime, all with < 25 lines of code.
How it Works β Introducing TemplateInfo
So how did this work, in terms of how the template is selected using the Html.EditorFor helper, nothing has changed from how our string Editor template worked. However to make use of jQuery UI and to make the template reusable, we need a way to know what element id will be created by the Html.TextBox helper.This is where TemplateInfo comes in, TemplateInfo is described as: "The TemplateInfo class is used to retrieve metadata for templates or HTML helpers that you create".
What this really means is the TemplateInfo class metadata is populated by the parameters we use when calling a Html helper.
From a template we access the TemplateInfo class via ViewDataDictionary. In our example the most important property is the HtmlFieldPrefix, as the name suggests this what's used to name a html field.
<%string name = ViewData.TemplateInfo.HtmlFieldPrefix;%>
If your curious you may have viewed the html source that the helper produces, for example for our DateOfBirth property:
<input id="DateOfBirth" name="DateOfBirth" type="text" />
The name used in the markup is generated by the Html helper and set to the value of the HtmlFieldPrefix. The HtmlFieldPrefix usually represents the name of the view data property that was passed by the Html helper.
Why not just hard code the name and id?
- Wouldn't make the template reusable and breaks the DRY principle.
- When calling a html helper, users of the template expect the html generated for name/id to change if a HtmlFieldPrefix is passed, for example Html.TextboxFor(model=>model.DateOfBirth,"DOB"), it is expected that the name and id will be changed to "DOB".
- Your model won't bind automatically, so your have to continue your hard coding into the controllers action.
- It won't work with nested classes or complex types and will require further redundant code in the controllers action.
What's the replace for?
<%string id = name.Replace(".", "_");%>
This is just to deal with nested classes, in the HtmlFieldPrefix nested classes are delimited by a dot, for example to select the Foo property of a nested class, in a html helper we would use Html.TextboxFor(model=>model.NestedClass.Foo), this sets the HtmlFieldPrefix to "NestedClass.Foo". However, the textbox id is set as "NestedClass_Foo", the replacement is my crude method to mimic this standard used by other Html helpers.
If you want to dig deeper into templates and their relationship with ModelMetadata refer to Brad Wilson's excellent post.
The jQuery UI bit?
Once we set up jQuery UI and we know that via ViewData.TemplateInfo.HtmlFieldPrefix, we can get the id of the textbox that the html helper generates, it's very easy to get the jQuery UI date picker working.We use the jQuery selector $("#<%=id%>") to select the textbox that our template will generate, and then we extend it with the datepicker, the rest of the parameters are just to format the date picker and are documented on the jQuery UI website.
<script type="text/javascript">
$(document).ready(function() {
$("#<%=id%>").datepicker({
showOn: 'both',
dateFormat: 'dd M yy',
changeMonth: true,
changeYear: true
});
});
</script>
YES, we are mixing server and client side code here and some people hate that concept, as we are only using the server side code to output the id of the textbox, I personally have no problem with this at all.
As this recipe demonstrates, with hardly any code using the TemplateInfo class and jQuery it's easy to create powerful reusable templates, this example including markup was less < 25 lines of code.
Data Annotations
Download the code for this exampleSlightly off-topic, but bare with me, data annotations (and other attributes in the System.ComponentModel namespace) can be used to annotate your model to affect the behavior of your views, templates and controllers.
Using data annotations to affect the behavior of your MVC application is known as convention over configuration. "Convention" means following a certain coding standard that the framework will recognize, the framework will provide "default behavior" matching the "convention". Flexibility is maintained by overriding the "default behavior" the framework provides. Traditionally config files or databases are used to configure behavior, the advantage of convention over configuration is we have less/simplified code without losing flexibility, it's self documenting and provides a coding standard.
Some people don't like this approach, however this is my preferred approach. Naysayers will say that this approach ties your model to your domain problem and prevents you from sharing a model that has different validation requirements etc. My argument to that is "You ain't gonna need it" (YAGNI), I'm not going to worry about a limitation (and add complexity) until it becomes a problem, then I'll find a solution.
I've used this technique in 5 production MVC applications and have only run into the model sharing problem once, if you do run into this problem an easy solution is to use "Buddy classes", Buddy classes allow you to provide separate class metadata when required.
OK after my long rant, a couple of quick examples - one to improve your template and one to add validation.
Looking at our Index view the labels don't look very user-friendly, for example FirstName would look better as First Name. We could hard-code the labels, but that would spoil all our hard work. Instead, all we are going to do is annotate our Employee model with the DisplayName attribute.
Change your employee model as follows:
public class EmployeeModel {
[DisplayName("First Name")]
public string FirstName { get; set; }
[DisplayName("Last Name")]
public string LastName { get; set; }
[DisplayName("Date of Birth")]
public DateTime? DateOfBirth { get; set; }
}
Debug/Employee/Index and you should see the following:
Pretty simple huh, the Html.LabelFor helper knows to pick up the label from our DisplayName attribute. Next we will use data annotations to add some basic validation.
Let's say we have a requirement to make both FirstName and LastName mandatory fields, but not DateOfBirth, forcing employees to give us their date of birth is just mean π
Again we will annotate our Employee model, this time with the "Required" attribute.
Change your employee model as follows:
public class EmployeeModel {
[Required]
[DisplayName("First Name")]
public string FirstName { get; set; }
[Required]
[DisplayName("Last Name")]
public string LastName { get; set; }
[DisplayName("Date of Birth")]
public DateTime? DateOfBirth { get; set; }
}
Next we need to add a couple of lines of code to the Index and Details actions:
[HttpGet]
public ActionResult Index(EmployeeModel model) {
return View(model);
}
[HttpPost]
public ActionResult Details(EmployeeModel model) {
if (ModelState.IsValid) {
return View(model);
}
else {
return RedirectToAction("Index", model);
}
}
Debug/Employee/Index, you should now not be able to submit the form without providing a FirstName and LastName.
How does this work?
We are using the Post/Redirect/Get (PRG) pattern, we changed the the Index action to take the EmployeeModel as a parameter, which we return as the action's model. In the Details action we added a if statement to check that the model is valid. If the model isn't valid or model errors are found, we redirect back to the Index action, passing the model and modelstate including any errors. Finally the view then displays the errors via the Html.ValidationMessageFor helper.I've just scratched the surface on this subject, for example you can add further validation using data annotations and regular expressions etc. There is loads of information on this subject, Scott Gu's post is a good place to start.
Complex Types
Download the code for this exampleThe last subject I want to touch on is complex types, I already mentioned the current limitations. This is just a short introduction to the workarounds, to get all your complex types flying with templates.
Our example so far is fine, but in the real world properties aren't always primitives strings, ints and so on, so what about our requirement to add multiple offices to an employee? We need a property called Offices that has a type of List<Office>.
First of all lets change our model to include the new property:
public class EmployeeModel {
[Required]
[DisplayName("First Name")]
public string FirstName { get; set; }
[Required]
[DisplayName("Last Name")]
public string LastName { get; set; }
[DisplayName("Date of Birth")]
public DateTime? DateOfBirth { get; set; }
public List Offices { get; set; }
}
Next we need to populate the office values by changing the \Employee\Index action, in a real situation you would get this from an infrastructure service such as a database.
[HttpGet]
public ActionResult Index(EmployeeModel model) {
if (model.Offices == null) {
model.Offices = new List(new Office[] {
new Office {
OfficeId = new Guid("ee70f79e-3840-4e8f-98eb-aa8ee12e254c"),
Address = "One Microsoft Way, Redmond",
Country = "United States",
PostalCode = "WA 98052"
},
new Office {
OfficeId = new Guid("0780b91a-7000-4860-a89c-ed6a1ea6c071"),
Address = "Microsoft Campus, Thames Valley Park, Reading, Berkshire",
Country = "United Kingdom",
PostalCode = "RG6 1WG"
},
new Office {
OfficeId = new Guid("db7a5076-c7c7-42f3-a6c6-1ead82af310a"),
Address = "Centro Direzionale San Felice, Palazzo A. Via Rivoltana, 13",
Country = "Italy",
PostalCode = "1320090 Segrate (MI)"
}
});
}
return View(model);
}
Finally, add the Html.EditorFor helper for the "Offices" property to our \Views\Employee\Index view:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcTempDemo1.Models.EmployeeModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Index</h2>
<% using (Html.BeginForm("Details","Employee")) {%>
<%= Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<%= Html.EditorFor(model => model.FirstName) %>
<%= Html.EditorFor(model => model.LastName) %>
<%= Html.EditorFor(model => model.DateOfBirth) %>
<%= Html.EditorFor(model => model.Offices) %>
<p><input type="submit" value="Create" /></p>
</fieldset>
<% } %>
<div><%= Html.ActionLink("Back to List", "Index") %></div>
</asp:Content>
Debugging \Employee\Index you will notice that the call to Html.EditorFor(model => model.Office) helper doesn't return anything we can use, this is because the MVC framework is trying to match a default template for our Offices property.
So let's create a shared template for our Offices property:
Add a view by right mouse clicking on the Views\Shared\EditorTemplates folder and select "Add View", when presented with the "Add View" dialog, do the following:
- Name the template "Offices"
- Check "Create a partial view"
- Check "Create a strongly-typed view"
- Type in "List<MvcTempDemo1.Models.Office>" as the view data class
Paste the following code into your template:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<List<MvcTempDemo1.Models.Office>>" %>
<div class="editor-label">
<%= Html.LabelFor(model => model) %>
</div>
<div class="editor-field">
<%= Html.ListBoxFor(model => model, new SelectList(Model, "OfficeId", "Country"))%>
<%= Html.ValidationMessageFor(model => model) %>
</div>
As you can see there is nothing complex about the template, you just create a template that presents the type as you require. However, if you debug \Employee\Index you will notice that the template is still not matched, this is because of the limitation I discussed before where the simple type name is used to select the template.
So how do we get the MVC framework to select our template?
There are a few options:
- Pass the template name to the helper as a string:
Html.EditorFor(model => model.Offices, "Offices")
- Use the attribute UIHint on your model property and supply the template name as a string:
[UIHint("Offices")]
public List Offices { get; set; }
- Use the attribute DataType on your model property and supply the a name of the type as a string:
[DataType ("Offices")]
public List Offices { get; set; }
Try one of the options above and debug \Employee\Index.
As you can see the template is selected and the listbox shown. So do I have a preferred method? Not exactly I use both the DataType attribute and passing the template name to the EditorFor helper.
I don't use UIHint, as you are supplying a template name in the model, for me that blurs the separation of concerns, yes you could argue that about the DataType attribute, but all you are doing with the DataType attribute is merely providing the name of your type as a string.
Are there any alternatives? Yes you could create your own html helper, Matt Hidinger has written an excellent post on how to do this.
Conclusion
What started out as a quick post, ended up as a mini book! I just can't help evangelizing about MVC 2. In my own production MVC application by refactoring the views into reusable templates, I was able to reduce the view's mark-up and code by more than 50%!I hope you have found this post useful and now consider yourself a template ninja. In my opinion templates will allow you to clean up your views and apply the same level of "pure" design as you would for the rest of your MVC application.