Home ASP Creating forms for deeply nested View Models in ASP.NET MVC

Creating forms for deeply nested View Models in ASP.NET MVC

by admin

More one interesting post from Jimmy Bogard , dedicated to creating forms for deeply nested View Models in ASP.NET MVC. Although it constantly refers to ASP.NET MVC 2, the information is also relevant to version 3. Below the hubrakat is the original post in a free translation.
ASP.NET MVC 2 introduced a setof strictly typed helpers for creating form elements in strictly typed views. These highly typed helpers use lambda expressions to create a fully prepared input element, including the correct name and value for the element.
Lambda expressions are quite expressive. They allow you to create sufficiently complex models to edit and have model binding to put everything together. An example of a complex view model type:

public class ProductEditModel
{
public string Name { get ; set ;}
public PriceEditModelPrice { get ; set ;}
public class PriceEditModel
{
public decimal Value { get ; set ; }
public string Currency { get ; set ; }
}
}
* This source code was highlighted with Source Code Highlighter

It’s easy enough to create a representation for him :

@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 >
}
* This source code was highlighted with Source Code Highlighter

As long as we use expressions built from the highest level of the model to create input elements, the correct HMTL will be obtained. Suppose you now want to pull PriceEditModel into a partial view and separate it from its parent view. We change the rendering of the property in our view to the rendering of the partial view :

@using (Html.BeginForm()) {
< p >
@Html.LabelFor(m = > m.Name)
@Html.TextBoxFor(m = > m.Name)
</ p >
@Html.Partial("_PriceEditModel", Model.Price);
}
* This source code was highlighted with Source Code Highlighter

Our partial view is just a cut of the view code, except that it is now based on 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 >
* This source code was highlighted with Source Code Highlighter

However, the resulting HTML no longer maps model members correctly. Even though everything is fine on the screen :
Creating forms for deeply nested View Models in ASP.NET MVC
But as soon as we look in the HTML, we see an error :
Creating forms for deeply nested View Models in ASP.NET MVC
Instead of our member name having a valid parent member in its name, like "Price.Currency", we only see "Currency". Indeed, when we get to the POST action, the Price member is null, because the model binding couldn’t find a match :
Creating forms for deeply nested View Models in ASP.NET MVC
Not exactly what we’d like to get!
So, what are our options? To make sure that model binding works for partial view models, we can bring those models in our partial views to the parent type. That is, change our model types for partial views from "PriceEditModel" to "ProductEditModel".
Not a very attractive option!
We have a better option – templated helpers from MVC 2. Template assistants elegantly solve the problem of deeply nested View Models.

Working with templated assistants

Template assistants differ from partial views in that they pass specific context information down from parent to child when we use the Html.EditorXyz() methods from HtmlHelper. To rebuild our views to use templated helpers, let’s just create an editor template for each view model we have :
Creating forms for deeply nested View Models in ASP.NET MVC
These templates are normal partial views from Razor, except that they are placed in a special EditorTemplates folder. For our partial view with ProductEditModel, we just move everything we had in our :

@model ProductEditModel
< p >
@Html.LabelFor(m = > m.Name)
@Html.TextBoxFor(m = > m.Name)
</ p >
@Html.EditorFor(m = > m.Price)
* This source code was highlighted with Source Code Highlighter

However, there is one minor detail here. Instead of rendering a partial representation of Price, we do a rendering editor for the Price member. The PriceEditModel template is what we had in our original partial view without any changes :

@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 >
* This source code was highlighted with Source Code Highlighter

At this point, the difference is that our templated helper knows that the parent model used the "Price" member to create the partial view. Things are even simpler in our parent Edit view :

@using (Html.BeginForm()) {
@Html.EditorForModel()
< input type ="submit" />
}
* This source code was highlighted with Source Code Highlighter

ASP.NET MVC will check the model type to make sure the editor template exists for that model type when we call the EditorForModel method. Since we create editor templates for each individual model type, it doesn’t matter where in the hierarchy these nested types are located. ASP.NET MVC will pass the parent context, so that the deeply nested View Model will have the correct information about it.
Looking at the resulting HTML, we can make sure that everything is OK :
Creating forms for deeply nested View Models in ASP.NET MVC
The input element name now has the correct parent property name as the value. And debugging the POST action confirms that the model binding is now working correctly :
Creating forms for deeply nested View Models in ASP.NET MVC
With templated helpers from ASP.NET MVC 2, we can create nested models in our views and still get all the benefits of partial views. The only caveat is to make sure you create views using templated helpers and Html.EditorXyz methods. Otherwise, the impact on your views will be minimal.
And just to complain – this method is really annoying in MVC 1.0. I threw out a bunch of code after I switched to older versions of MVC!

You may also like