Collections and ASP.NET MVC Templated Helpers (DisplayFor/EditorFor)


This is part of a mini-series:

  • Part 1 – Define the problem & give a workaround
  • Part 2 – Show an alternative workaround
  • Part 3 – Show a reusable, simple solution
  • Part 4 - Replacing Object.ascx

I’ve been spending quite a lot of time with ASP.NET MVC 2 recently, talking to customers about it and working on Proof-of-concept projects. When I first looked at the templated helpers that are new in version 2 I was slightly underwhelmed, but over time they have grown on me hugely; they provide a great way to rapidly get some UI created and then allow you to gradually tweak the behaviour as you require (right down to replacing the whole of the generated HTML if you so desire). Additionally, I find that the emphasis on partial views helps to promote UI modularity and re-use. (For an introduction to templated helpers I’d recommend Brad Wilson’s series)

Anyway, I was recently working with the templated helpers and hit a stumbling block so I thought I’d write about it here so that I can find the solution when I next encounter the problem. I should probably note that despite the title of the post, this problem occurs with any property type that isn’t convertible from a string, not just collections – so even if you are experiencing this issue with a non-collection type, keep reading!

In my scenario I’ve got a model class along the lines of the following:

public class Foo
{
    public int Id { get; set; }
    public List<Bar> Bars { get; set; }
}

and in my view I’m using the templated helpers to generate the UI:

<%= Html.DisplayForModel() %>

When the view is displayed, only the Id field is output. This is no massive surprise as Bars is a collection. What I had expected to be able to do was to create a partial view (BarList.ascx) and annotate the model to instructed the templated helpers to use it:

public class Foo
{
    public int Id { get; set; }
    [UIHint("BarList")]
    public List<Bar> Bars { get; set; }
}

With this in place I re-ran the web site and... no change!

Digging deeper – custom collections and type converters

Since this is ASP.NET MVC, the source code is available so my next step was to crack it open in Visual Studio and start having a dig through it. Looking at ModelMetadata there is a notion of a complex type; basically a type is considered simple if it can be converted from a string and everything else is complex. In the templated helpers, complex properties are filtered out before the UIHint is even considered, which explains the before I saw above.

Suppose for a moment that instead of List<Bar> I had used a custom type (e.g. BarList) as my collection type:

public class Foo
{
    public int Id { get; set; }
    public BarList Bars { get; set; } // <-- Custom collection type
}
public class BarList : List<Bar>
{
}

Then through the magic of TypeConverters, I would be able to decorate BarList with a TypeConverter attribute:

[TypeConverter(typeof(BarListTypeConverter))]
public class BarList : List<Bar>
{
}

and then provide an implementation of BarListTypeConverter:

public class BarListTypeConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        bool canConvertFrom = base.CanConvertFrom(context, sourceType);
        return canConvertFrom;
    }
}

With these changes when the ModelMetadata queries to see whether Foo.Bars is convertible from a string it will end up calling BarListTypeConverter which will return true. So ModelMetadata will determine that Foo.Bars is a simple type rather than a complex type and I will be able to use a UIHint to specify the template to use:

public class Foo
{
    public int Id { get; set; }
    [UIHint("BarList")]
    public BarList Bars { get; set; }
}

(In the example above, the UIHint is actually unnecessary as the templated helper will look for a template based on the type name anyway, but you could specify “SomeOtherBarList” if you wanted!)

So, we now have a way round the issue, but it requires us to use a custom collection type (so that we can attach the TypeConverter attribute). Stay tuned… next time I will post an alternative solution that will work if you don’t want to/can’t use a custom collection type!

Comments (5)

  1. G-Mac says:

    This issue kicked my butt yesterday.  I was going to roll up the sleeves and dig in to the MVC code, but now I don't have to.  Thanks!  🙂

  2. stuartle says:

    Hi G-Mac – glad it was useful!

  3. kondotine says:

    Hello, good post.

    But i think the reason why your Bar Collection wasn't  displayed, even when creating the partial view "BarList.ascx" and using the attribute "UIHint"  was that your "BarList.ascx" wasn't in the right folder.

    As said in the MSDN (http://tinyurl.com/2a4rqmu), display ( resp. editor) templates are looked :

    -First, in the controller's  DisplayTemplates (resp. EditorTemplates) folder,

    -If not found there, then in the ViewsSharedEditorTemplates (resp. ViewsSharedEditorTemplates) folder.

    -If no template folder is found, the default template is used.

    Can you confirm?

  4. stuartle says:

    Hi kondotine,

    You are correct that the template has to be in the correct sub-folder (i.e. DisplayTemplates or EditorTemplates), but in this case the default template doesn't even look for the temlpate because it sees the Bars propery as a complex property. It only dives down one level on complex properties and has already considered Foo, so it stops there.

    – Stuart

  5. Cosic says:

    Great article! This is the only article I found on this issue.

    Thanks an my compliments.

Skip to main content