📜 ⬆️ ⬇️

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

Another interesting post by Jimmy Bogard , dedicated to creating forms for deeply nested View Model in ASP.NET MVC. Despite the fact that it constantly refers to ASP.NET MVC 2, the information is also relevant for the 3rd version. Under a habrakat an original post in a free translation.


ASP.NET MVC 2 introduced a set of strongly typed helpers for creating form elements in strongly typed views. These strongly typed helpers use lambda expressions to create a complete input element, including the correct name and value for the element.

Lambda expressions are quite expressive. They allow you to create quite complex models for editing and have a model binding in order to put everything together. An example of a complex type view model:
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 ; }
}
}

* This source code was highlighted with Source Code Highlighter .

It's easy enough to create a presentation for it:
@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 constructed from the top level of the model to create input elements, the correct HMTL will be obtained. Suppose you now want to pull the PriceEditModel into a partial view and separate it from the parent view. In our view, we change the rendering properties to render 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 simply a cut-out 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 matches the members of the model correctly. Despite the fact that everything is fine on the screen:

')
But if we look at the HTML, we will see an error:


Instead of the name of our member who has the correct parent member in his name, like “Price.Currency”, we see only “Currency”. Indeed, when we get into the POST action (action), the Price member is null, because Model binding could not find a match:


Not exactly what we would like to receive!

So what are our options? To make sure that the model binding works for models with partial representations, we can bring these models in our partial representations to the parent type. Those. replace our model types for partial views with “PriceEditModel” to “ProductEditModel”.

Not a very attractive option!

We have a better option - template helpers from MVC 2. Template helpers elegantly solve the problem of deeply nested View Model.

Working with template helpers


Template helpers differ from partial views in that special contextual information in them is passed down from parent to child when we use the Html.EditorXyz () methods from HtmlHelper. In order to rebuild our views to use template helpers, let's just create an editor template for each view model that we have:


These templates are the usual partial views from Razor, except that they are placed in the special folder EditorTemplates. For our partial view with ProductEditModel, we simply move everything we had in our view:
@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. Instead of rendering a partial view of Price, we render the 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 the moment, the difference is that our templating helper knows that the parent model used the “Price” member to create a partial view. In our parent view, Edit is still simpler:
@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 that the editor template exists for this type of model when we call the EditorForModel method. Because we create editor templates for each individual type of model; it does not matter where these nested types are located in the hierarchy. ASP.NET MVC will transfer the parent context, so that the deeply nested View Model will have the correct information about it.

After reviewing the resulting HTML, we can make sure that everything is in order:


The name of the input element now has the correct name of the parent property as the value. And debugging the POST action confirms that the model binding now works correctly:


With the template helpers from ASP.NET MVC 2, we can create nested models in our views and, at the same time, get all the benefits of partial views. The only caveat is to make sure you create views using templating helpers and Html.EditorXyz methods. Otherwise, the impact on your submission will be minimal.

And just to complain - this method is very annoying in MVC 1.0. I threw out a bunch of code after I switched to older versions of MVC!

Source: https://habr.com/ru/post/128026/


All Articles