Displaying Data Flexibly with Custom View Templates in ASP.NET MVC
You don't always want to display the same data the same way. Here are your options for leveraging custom templates in Views to meet all of your needs -- and the code you need when a template won't do the job.
In another column, I showed how custom templates simplify your Views and better support the Model-View-Controller design pattern. And that's true…provided you want to display a particular class' information the same way everywhere it appears in your site. Unfortunately, in the real world, you don't always want to display the same class the same way. But, fortunately, custom templates can give you that flexibility, also. And where custom templates aren't flexible enough, you can write some code.
Generally speaking, consistency is a virtue in UI design and users appreciate it when the same thing is displayed the same way everywhere it appears. As I showed in my previous column, you can achieve that consistency by creating a custom template for any class whose data you're displaying in a View.
However, you may not want to display the same class identically in every View where the class appears. You might, for example, want to display sales orders as sent to a vendor differently from sales orders as received from a customer because some sales order information that's appropriate for customers isn't appropriate for vendors (or just because users also appreciate when different things are displayed differently).
For example, to drive Customer Sales Order and Vendor Sales Order Views, I would create two data transfer objects (DTOs): CustomerDTO and VendorDTO. Both DTOs would have a property that holds a SalesOrder object. By default, for any class, ASP.NET MVC looks for a custom template with the same name as the class: When displaying a SalesOrder class, ASP.NET looks for a template called SalesOrder. But I can override that default choice by decorating the property that holds the class with a UIHint that provides the name for a different template.
To have the SalesOrder class in my VendorDTO be displayed using a template called VendorOrder, I add a UIHint like this to the property holding the SalesOrder in my VendorDTO:
Public Class VendorDTO
Public Property() Id As String
Public Property SalesOrder() As SalesOrder
Public Property Vendor() As VendorInfo
Public Property SalesOrderDetails() As List(of SalesOrderDetail)
This UIHint will now apply when my VendorDTO is used in any View in the application. This solution, therefore won't handle the scenario where I want to display a Vendor SalesOrder differently on a single page (a page displaying a Vendor SalesOrder in error, for example). To handle that, I can pass the name of the template to use as the second parameter to the EditorFor method, like this:
Unfortunately, neither UIHints nor specifying a template name with the EditorFor method will work with a collection. If my DTO contains a collection of SalesOrders (rather than just one), then using either of these solutions will generate an error. If you want to display the members of a collection with a different template than the default, then you'll have to write a loop that displays each member of the collection individually. In that scenario a template name supplied through a UIHint applied to the class or passed to the EditorFor method will work.
Here's an example that uses a different template than the default for a CustomerAddresses collection by passing a template name to the EditorFor method:
@For i = 0 To Model.CustomerAddresses.Count - 1
Writing It Yourself
As you create these alternate templates, it can mean that your EditorTemplates folder is going to fill up with "one off" templates that are only used in a single View. If that bothers you, you can abandon the EditFor method all together and just write out your HTML tags yourself. The key issue is to ensure that name attribute in the tag is set to a value that allows ASP.NET MVC to match up with the property on the DTO. Typically, this just means that you need to set the name attribute to the class name, followed by a period, followed by the property name. This example ties an input tag's value to the Id property on a class called Vendor:
<input type="text" name="VendorDTO.Id" ...
For nested classes (like my Customer object nested inside my SalesOrderDTO class) you'll need to provide the full path to the property:
<input type="text" name="SalesOrderDTO.Customer.FirstName" ...
For a repeating element like my CustomerAddresses you need to include an index for the position of the item. For the first Street item in the CustomerAddresses collect, the name attribute would look like this:
<input type="text" name="Customer.CustomerAddresses(0).Street" ...
Typical code to generate the list of CustomerAddresses would look like the code in Listing 1.
Listing 1: Typical Code to Generate List of CustomerAddresses
Dim pos As Integer
Dim name As String
@For Each addr As CustomerInformation.Address In Model.CustomerAddresses
name = "CustomerAddresses[" & pos & "].City"
@<input type="text" name="@name" value="@addr.City"/>@<br/>
name = "CustomerAddresses[" & pos & "].Street"
@<input type="text" name="@name" value="@addr.Street"/>@<br/>
name = "CustomerAddresses[" & pos & "].AddressType"
@<input type="text" name="@name" value="@addr.AddressType"/>@<br/>
pos += 1
I do like custom templates and, through UIHints and the EditFor method, I can have just about all the flexibility I would ever want. And, when I can't have what I want, I can use code.
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.