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!

Integrating Toto and a Sinatra app in 10 steps