The expressiveness of WPF is amazing. I particularly like the databinding and templating features. The ability to selectively render an object based on its type is very strong.

When I recently began working with ASP.NET MVC (which I like so far), I quickly ran into a scenario where I would have liked to have WPF's DataTemplates at my disposal. Maybe it's just because I've become used to WPF, but I missed the feature and set out to find out if something similar is possible in ASP.NET MVC.

Before we dive into that, I'd like to present the 'problem' in WPF terms, but the underlying View Model that I want to expose will be shared between both solutions.

PresentationModel

The main point is that the Items property exposes a polymorphic list. While all items in this list share a common property (Name), they are otherwise different; one contains a piece of Text, one contains a Color, and one is a complex item that contains child items.

When I render this list, I want each item to render according to its type.

In WPF, this is fairly easy to accomplish with DataTemplates:

<ListBox.Resources>
    <DataTemplate DataType="{x:Type pm:NamedTextItem}">
        <StackPanel>
            <TextBlock Text="{Binding Path=Name}"
                       FontWeight="bold" />
            <TextBlock Text="{Binding Path=Text}" />
        </StackPanel>
    </DataTemplate>
    <DataTemplate DataType="{x:Type pm:NamedColorItem}">
        <StackPanel>
            <TextBlock Text="{Binding Path=Name}"
                       FontWeight="bold" />
            <Ellipse Height="25" Width="25"
                     HorizontalAlignment="Left"
                     Fill="{Binding Path=Brush}"
                     Stroke="DarkGray" />
        </StackPanel>
    </DataTemplate>
    <DataTemplate DataType="{x:Type pm:NamedComplexItem}">
        <StackPanel>
            <TextBlock Text="{Binding Path=Name}"
                       FontWeight="bold" />
            <ListBox ItemsSource="{Binding Path=Children}"
                     BorderThickness="0">
                <ListBox.Resources>
                    <DataTemplate
                        DataType="{x:Type pm:ChildItem}">
                        <TextBlock
                            Text="{Binding Path=Text}" />
                    </DataTemplate>
                </ListBox.Resources>
            </ListBox>
        </StackPanel>
    </DataTemplate>
</ListBox.Resources>

Each DataTemplate is contained within a ListBox. When the ListBox binds to each item in the Items list, it automatically picks the correct template for the item.

The result is something like this:

WpfApp

The NamedTextItem is rendered as a box containing the Name and the Text on two separate lines; the NamedColorItem is rendered as a box containing the Name and a circle filled with the Color defined by the item; and the NamedComplexItem is rendered as a box with the Name and each child of the Children list.

This is all implemented declaratively without a single line of imperative UI code.

Is it possible to do the same in ASP.NET MVC?

To my knowledge (but please correct me if I'm wrong), ASP.NET MVC has no explicit concept of a DataTemplate, so we will have to mimic it. The following describes the best I've been able to come up with so far.

In ASP.NET MVC, there's no declarative databinding, so we will need to loop through the list of items. My View page derives from ViewPage<MyViewModel>, so I can write

<% foreach (var item in this.Model.Items)
   { %>
   <div class="ploeh">
   <% // Render each item %>
   </div>
<% } %>

The challenge is to figure out how to render each item according to its own template.

To define the templates, I create a UserControl for each item. The NamedTextItemUserControl derives from ViewUserControl<NamedTextItem>, which gives me a strongly typed Model:

<div><strong><%= this.Model.Name %></strong></div>
<div><%= this.Model.Text %></div>

The other two UserControls are implemented similarly.

A UserControl can be rendered using the RenderPartial extension method, so the only thing left is to select the correct UserControl name for each item. It would be nice to be able to do this in markup, like WPF, but I'm not aware of any way that is possible.

I will have to resort to code, but we can at least strive for code that is as declarative in style as possible.

First, I need to define the map from type to UserControl:

<%
    var dataTemplates = new Dictionary<Type, string>();
    dataTemplates[typeof(NamedTextItem)] =
        "NamedTextItemUserControl";
    dataTemplates[typeof(NamedColorItem)] =
        "NamedColorItemUserControl";
    dataTemplates[typeof(NamedComplexItem)] =
        "NamedComplexItemUserControl";
%>

Next, I can use this map to render each item correctly:

<% foreach (var item in this.Model.Items)
   { %>
   <div class="ploeh">
   <% // Render each item %>
   <% this.Html.RenderPartial(dataTemplates[item.GetType()],
                            item); %>
   </div>
<% } %>

This is definitely less pretty than with WPF, but if you overlook the aesthetics and focus on the structure of the code, it's darn close to markup. The Cyclomatic Complexity of the page is only 2, and that's even because of the foreach statement that we need in any case.

The resulting page looks like this:

AspNetMvcApp

My HTML skills aren't good enough to draw circles with markup, so I had to replace them with blocks, but apart from that, the result is pretty much the same.

A potential improvement on this technique could be to embed the knowledge of the UserControl into each item. ASP.NET MVC Controllers already know of Views in an abstract sense, so letting the View Model know about a UserControl (identified as a string) may be conceptually sound.

The advantage would be that we could get rid of the Dictionary in the ViewPage and instead let the item itself tell us the name of the UserControl that should be used to render it.

The disadvantage would be that we lose some flexibility. It would then require a recompilation of the application if we wanted to render an item using a different UserControl.

The technique outlined here represents an explorative work in progress, so comments are welcome.


Comments

David C
In MVC, data templates are really either Editor or Display templates. In your Shared Views Folder, you can create an EditorTemplates folder and a DisplayTemplates folder. These are snippets which can be either ASP or Razor syntax. By default they run on convention. So if you create a String.cshtml, then anytime you do a Html.EditorFor(x=>x.SomeModelVariableThatIsAString) it will automatically use your template for it. If you only need to display it, not edit it, you can do Html.DisplayFor(x=>x.SomeModelVariableThatIsAString). Assuming you have a String.cshtml in your DisplayTemplates folder as well. You can also optionally specify a specific template, such as Html.DisplayFor(x=>x.SomeModelVariableThatIsAString,"PrettyListItem"), and it will look for PrettyListItem.cshtml as the template to render your <li>. Generally you would simply add a css class name to the markup in that instance of the template <li class="purty">, and do all your styling in CSS, which is of course optimal for Web development.

Here is a fairly detailed introduction to using Templates in MVC. It is a bit old but the concepts still hold.
http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html

BTW: Your custom implementation was pretty tight, not bad at all :)
2012-09-05 19:38 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or Google Plus, or somewhere else with a permalink. Ping me with the link, and I may add it as a comment.

Published

Thursday, 16 July 2009 19:33:48 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!