Practical .NET

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
  <UIHint("VendorOrder">
  Public Property SalesOrder() As SalesOrder
  Public Property Vendor() As VendorInfo
  Public Property SalesOrderDetails() As List(of SalesOrderDetail)
End Class

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:

@Html.EditorFor(Function(v) v.SalesOrder,"ErrorOrder")

Handling Collections
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
  @Html.EditorFor(Function(c) c.CustomerAddresses(i),"OtherAddress")
Next

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
@Code
  Dim pos As Integer
  Dim name As String
End Code

@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
Next

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.

About the Author

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/.

comments powered by Disqus

Featured

  • VS Code Copilot Previews New GPT-4o AI Code Completion Model

    The 4o upgrade includes additional training on more than 275,000 high-quality public repositories in over 30 popular programming languages, said Microsoft-owned GitHub, which created the original "AI pair programmer" years ago.

  • Microsoft's Rust Embrace Continues with Azure SDK Beta

    "Rust's strong type system and ownership model help prevent common programming errors such as null pointer dereferencing and buffer overflows, leading to more secure and stable code."

  • Xcode IDE from Microsoft Archrival Apple Gets Copilot AI

    Just after expanding the reach of its Copilot AI coding assistant to the open-source Eclipse IDE, Microsoft showcased how it's going even further, providing details about a preview version for the Xcode IDE from archrival Apple.

  • Introduction to .NET Aspire

    Two Microsoft experts will present on the cloud-native application stack designed to simplify the development of distributed systems in .NET at the Visual Studio Live! developer conference coming to Las Vegas next month.

  • Microsoft Previews Copilot AI for Open-Source Eclipse IDE

    Catering to Java jockeys, Microsoft is yet again expanding the sprawling reach of its Copilot-branded AI assistants, previewing a coding tool for the open-source Eclipse IDE.

Subscribe on YouTube

Upcoming Training Events