ASP.NET MVC Complex Object ModelMetaData Issue

One of the exciting feature of ASP.NET MVC is(?) auto generating the view of the model with DisplayForModel/EditorForModel statement, it gives a nice extension point for the application developers to customize the view. But in the latest release (MVC2 RC2), it looks like the feature is no longer available. No, those methods are still available, but they are not working the same way as they were doing in the earlier versions. In rc2, if your model has any complex object, it will not render it . I know some of you like myself thought, one of the major changes in rc2 is the Input Validation vs. Model Validation in ASP.NET MVC and rest of it is bug fixing and some improvements. I did check the release note doc, where it is mentioned as

Templated helpers such as Html.EditorFor and Html.DisplayFor show only simple properties by default. If you need to show complex properties, you can create a custom template to show any set of properties.

Check the last line, it says to show the complex properties we have to create a custom template. But interestingly enough it does not work. Let me show you a very simple example, lets say that we have product and category class like the following:

public class Category
{
    public int Id { get; set; }

    public string Name { get; set; }
}

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; }

    public decimal Price { get; set; }

    [UIHint("CategoryTemplate")]
    public Category Category { get; set; }
}

Check that we are using a custom template (CategoryTemplate) to show the category property.

And in the Controller:

public ActionResult Index()
{
    return View(new Product
                 {
                     Id = 1,
                     Name = "Product 1",
                     Price = 10.0m,
                     Category = new Category { Id = 2, Name = "Category 2" }
                 });
}

And in the View we want to render the product:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ComplexObject.Models.Product>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Home Page
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <p>
        <%= Html.DisplayForModel() %>
    </p>
    <p>
        To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
    </p>
</asp:Content>

But when the view is rendered, there is no category:

CO1

I posted this issue in the Gu’s RC2 announcement post as well as in the CodePlex, but the answer that I got is somewhat confusing. I do not think this is a right decision to hide the complex object in the default implementation, I never hard anybody is having some issues with this features and I do not think it is the right time for this change as the MVC2 is in in RC, anyway feature is broken and needs to be resolved in the first place.

So, till there is a fix, what is the workaround? Lets write some Haacky code. First, lets see where the ASP.NET MVC take the decision to show/hide the model property. If you open the DefaultDisplayTemplates.cs and DefaultEditorTemplates.cs under the Html folder of the ASP.NET MVC Source code, you will find both of it has a method called ShouldShow.

private static bool ShouldShow(ModelMetadata metadata, TemplateInfo templateInfo)
{
    return
        metadata.ShowForEdit
        && metadata.ModelType != typeof (EntityState)
        && !metadata.IsComplexType
        && !templateInfo.Visited(metadata);
}

The only problematic checking is the IsComplexType which in turns check whether the type can be converted from the string type :

public virtual bool IsComplexType
{
    get { return !(TypeDescriptor.GetConverter(ModelType).CanConvertFrom(typeof (string))); }
}

So the workaround is either we override the ModelMetadata which is somewhat an overkill or we can create a TypeConverter for the complex object for which we have custom template to fool the ASP.NET MVC Framework. In this case it is the Category. So lets write a converter.

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

Now, decorate the Category with this converter:

[TypeConverter(typeof(CategoryConverter))]
public class Category
{
    public int Id { get; set; }

    public string Name { get; set; }
}

when you run the project you will find the category is shown like the following:

CO2

And that’s it. You can download the complete code from the following link.

Source Code: ComplexObject.zip

Shout it

8 Comments

Comments have been disabled for this content.