Building forms for deep View Model graphs in ASP.NET MVC

ASP.NET MVC 2 introduced a multitude of strongly-typed helpers for building form elements for strongly-typed views. These strongly-typed helpers use lambda expressions to build a complete input element, including the correct name and value for the element.

The lambda expressions are quite powerful, allowing you to build quite complex edit models and have model binding put everything back together again. A complex view model type such as:

public class ProductEditModel
{
    public string Name { get; set; }
    public PriceEditModel Price { get; set; }

    public class PriceEditModel
    {
        public decimal Value { get; set; }
        public string Currency { get; set; }
    }
}

Can be quite easily built in a view:

@using (Html.BeginForm()) {
    <p>
        @Html.LabelFor(m => m.Name)
        @Html.TextBoxFor(m => m.Name)
    </p>
    <p>
        @Html.LabelFor(m => m.Price.Currency)
        @Html.TextBoxFor(m => m.Price.Currency)
    </p>
    <p>
        @Html.LabelFor(m => m.Price.Value)
        @Html.TextBoxFor(m => m.Price.Value)
    </p>
}

As long as we’re using the full model expression from the top-most model type to build the input elements, the correct HTML will be built. Suppose that you want to now pull that PriceEditModel out into a partial, and keep its view separate from the parent view. We change our view to instead render a partial for the Price property:

@using (Html.BeginForm()) {
    <p>
        @Html.LabelFor(m => m.Name)
        @Html.TextBoxFor(m => m.Name)
    </p>
    @Html.Partial("_PriceEditModel", Model.Price);
}

And our partial is just the extracted view code, except now built against the PriceEditModel type:

@model ProductEditModel.PriceEditModel

<p>
    @Html.LabelFor(m => m.Currency)
    @Html.TextBoxFor(m => m.Currency)
</p>
<p>
    @Html.LabelFor(m => m.Value)
    @Html.TextBoxFor(m => m.Value)
</p>

However, the resultant HTML no longer matches up the model members correctly. Although the screen looks right:

image

When we look at the actual HTML, something’s not right any more:

image

Instead of our member name having the correct parent member in its name as “Price.Currency”, we only see “Currency”. Sure enough, when we get to our POST action, the Price member is null as model binding could not line things up any more:

image

Not exactly what we want to do here!

So what are our options? In order to make sure model binding works for models with partials, we can scope our models in our partials to the parent type. That is, make our partial’s model type “ProductEditModel” instead of “PriceEditModel”.

Not a very appealing option!

We do have a better option, with the MVC 2 feature of templated helpers. Templated helpers elegantly solve the deep View Model graph problem.

Building with templated helpers

Templated helpers are different than partials in that special contextual information from the parent is passed down to the child as long as we’re using the Html.EditorXyz() HtmlHelper methods. To convert our view to use templated helpers, let’s just build an editor template for each view model type we have:

image

These are just normal partials in Razor, with the exception that they’re placed in the special EditorTemplates folder. In our ProductEditModel partial, we just move what we had in our Edit view over:

@model ProductEditModel

<p>
    @Html.LabelFor(m => m.Name)
    @Html.TextBoxFor(m => m.Name)
</p>
@Html.EditorFor(m => m.Price)

There is one slight difference here, however. Instead of rendering a partial for Price, we render the editor for the Price member. The PriceEditModel template is just what we had in our original partial with no changes needed:

@model ProductEditModel.PriceEditModel

<p>
    @Html.LabelFor(m => m.Currency)
    @Html.TextBoxFor(m => m.Currency)
</p>
<p>
    @Html.LabelFor(m => m.Value)
    @Html.TextBoxFor(m => m.Value)
</p>

The difference now is that our templated helper knows that the parent model used the “Price” member to build out this partial. In our parent Edit view, things become a bit simpler:

@using (Html.BeginForm()) {
    
    @Html.EditorForModel()

    <input type="submit" />
    
}

ASP.NET MVC will look at the type of the model to see if an editor template exists for that specific model type when we call the EditorForModel method. Because we built editor templates for each specific model type, it doesn’t matter where in the hierarchy these nested types exist. ASP.NET MVC will keep passing in the parent’s context so that deep nested graphs contain the right context information.

Looking at the resultant HTML, we can confirm that everything looks good there:

image

The input element’s name now has the correct parent property name in its value. Debugging into the POST action confirms that the model binding now works correctly:

image

With the templates helpers of ASP.NET MVC 2, we can now build nested models in our views and still take advantage of features like partials. The only caveat is that we need to make sure we build our views using templates helpers and the Html.EditorXyz methods. Otherwise, our views are minimally impacted.

And just to get a side complaint in, this was very annoying to build in MVC 1.0, to build nested hierarchies with strongly typed helpers respecting graphs including collection types all the way down. Just more code I got to delete with the later versions of MVC!

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASP.NET MVC. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://www.deltashoppe.com Imran Rashid

    Wow, great explanation. I have been burned by this before.

  • http://twitter.com/hazzik Alexander I. Zaytsev

    Hm, just Put Object.cshtml into /Views/Shared/EditorTempltes

    https://gist.github.com/1200683

    And you don’t need custom typed templates ever!

    This is all because default object template has restriction on tempates depth.

    • http://twitter.com/luizbon Luiz Adilson S Bon

      This aproeach is good for a generic template to use with scafolding for example. The typed ones should be used when we want some particular rendering.

      • http://twitter.com/hazzik Alexander I. Zaytsev

        Yes, you are right. By the way we write custom typed templates very-very rarely.

  • http://chadmyers.lostechies.com Chad Myers

    Another approach might be to use HtmlTags and HtmlConventions (it’s not just for FubuMVC anymore!).   They work for ASP.NET MVC as well and might help with this problem for small chunks of HTML

  • Martin

    Wow,  thanks for the post. I’ll try it out tonight. I was struggling with this very issue last night!!!

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #933

  • Livingston

    Great post. This almost totally removes the need for partial view

  • http://www.brabantnatuursteen.nl Natuursteen

    It’s almost taken away the partial view so I like this code very much. Thanks fort putting in the effort!

  • Abc
    • Anonymous

      In that case, we’re filling in the TemplateInfo manually with magic strings, which EditorFor does for us already. That way works, and has its place, but I think this way is easier to use.

    • Anonymous

      In that case, we’re filling in the TemplateInfo manually with magic strings, which EditorFor does for us already. That way works, and has its place, but I think this way is easier to use.

  • Alex Gil

    Would it work if you have a 3 level graph? Following your example, say PriceEditModel had a property of type CurrencyModel.

    • Anonymous

      Yes, it does. As long as you use the EditorForXYZ methods, you’re good.

    • Anonymous

      Yes, it does. As long as you use the EditorForXYZ methods, you’re good.

  • Tohid Azizi

    I had always problem with Client-Side Unobtrusive Validation with complex classes. Hopefully this approach would fix it…

    • Igo

      I have the same question , one of my nested classes has required attributes, but the client side validation doesnt work, i mean the textboxes are generated but without the validation set to true etc … any idea

      • gencvolkan

        I made AccountViewModel and UserViewModel. In AccountViewModel i am calling UserViewModel like User. So it renders above code like “User.PassWord”. But in jquery 1.8.2 it gives error for the dot “.” , In some websites says : you should use “.” with backslashes.

        Error: Syntax error, unrecognized expression: :input[name=User.PassWord]

        @Html.TextBoxFor(m => m.User.PassWord, new Dictionary() { { “class”, “span6″ }, { “type”, “password” }})

        How can i resolve this problem , I dont want to move username and pasword to parent class….

        • jbogard

          Hmmm, I’m not getting that error on my side. Why not use the ID then? That’s the reason the underscore is there.

          • gencvolkan

            Hi jbofgard, yes you are right … I find the problem. The problem occurs [Compare] attribute for the password.

            http://stackoverflow.com/questions/6818516/compare-password-attribute

            I modified “jquery.validate.unobtrusive” js file and replaced

            element = $(options.form).find(“:input[name=" + fullOtherName + "]“)[0];

            to

            element = $(options.form).find(“:input[name=" + fullOtherName.replace(".", "\.") + "]“)[0];

  • name

    xyz

  • MemTech Lodhi

     Nice post, thanks for sharing with us. It’s really helpful for me as well as this link
    http://www.mindstick.com/Articles/b76e5898-693b-40d3-930b-a9220bda5b15/?Model%20in%20ASP.NET%20MVC
    also helped me to complete my task.

    Thanks !!!

  • Pingback: How to handle multiple instances of a model on one view

  • http://radwantfs.myopenid.com/ M.Radwan

    I have a question here, you make the Edit price template use  @model ProductEditModel.PriceEditModel

    that’s mean I can’t use the price editor template in another parent view which break the reason of creating the template itself??? we use the template so we can use it in many places, so my question why I use this template if I always render the template in one view which is edit product?? and what to do if I want the same solution but render this template in many views?

    Or maybe I miss something :-) , so please explain

    Thanks

    • Anonymous

      The reason for creating child templates isn’t just for re-use. Think of it similar to private methods in a class. Sometimes you want to just break big pieces into little pieces so that it’s easier to understand the whole picture.

  • http://www.dotnetjalps.com/ Jalpesh Vadgama

    good work!! I found what I was searching for.. Thanks for sharing!!

  • Lone Truth

    Great tutorial! Can you make this work with a dropdownlist inside the editor template?

  • Akshay Srinivasan

    Can you provide an article or link to an article that accomplishes this in ASP.NET MVC 4 with ADO.NET Entity Framework 5.0 with the addition of showing the data pushed to SQL Server please.

    • jbogard

      I don’t use EF 5.0, sorry. You might want to go back to the ASP.NET MVC website, they have tons of examples showing full end-to-end examples.