Column MVC Null Model Using Partial View

I have an MVC controller where the model of the post method always returns as null. I am not sure if this is because I use a partial view in the form.

Any idea why the model is not returning to the controller?

Model

enter image description here

Download Model

public List<Group> GetStaticMeasures(int businessUnitID) { List<Group> groups = ctx.Groups .Include("Datapoints") .Where(w => w.BusinessUnitID.Equals(businessUnitID)) .OrderBy(o => o.SortOrder).ToList(); groups.ForEach(g => g.Datapoints = g.Datapoints.OrderBy(d => d.SortOrder).ToList()); return groups; } 

controller

 public ActionResult Data() { ViewBag.Notification = string.Empty; if (User.IsInRole(@"xxx\yyyyyy")) { List<Group> dataGroups = ctx.GetStaticMeasures(10); return View(dataGroups); } else { throw new HttpException(403, "You do not have access to the data."); } } [HttpPost] [ValidateAntiForgeryToken] public ActionResult Data(List<Group> model) { ViewBag.Notification = string.Empty; if (User.IsInRole(@"xxx\yyyyyy")) { if (ModelState.IsValid) { ctx.SaveChanges(model); ViewBag.Notification = "Save Successful"; } } else { throw new HttpException(403, "You do not have access to save the data."); } return View(model); } 

Main view

 @model List<Jmp.StaticMeasures.Models.Group> <div class="row"> @using (Html.BeginForm()) { @Html.AntiForgeryToken() @Html.ValidationSummary(true) <div class="large-12"> <div class="large-8 large-centered columns panel"> @foreach (var g in @Model) { <h2>@g.Name</h2> foreach (var d in g.Datapoints) { @Html.Partial("Measures", d) } <hr /> } <input type="submit" class="button" value="Save Changes"/> </div> </div> } </div> 

Partial view

 @model Jmp.StaticMeasures.Models.Datapoint @Html.HiddenFor(d => d.ID) @Html.HiddenFor(d => d.Name) @Html.HiddenFor(d => d.SortOrder) @Html.DisplayTextFor(d => d.Name) @Html.EditorFor(d => d.StaticValue) @Html.ValidationMessageFor(d => d.StaticValue) 

Highlighted Html showing consecutive identifiers

enter image description here

+7
c # asp.net-mvc asp.net-mvc-4
source share
3 answers

As you correctly noted, this is due to the fact that you are using partial. This is because Html.Partial does not know that it works in the collection, so it does not generate names for your form elements with the intention of binding them to the collection.

However, the fix in your case seems pretty simple. Instead of using Html.Partial , you can simply change your fragment in the EditorTemplate and call Html.EditorFor in this template. Html.EditorFor is smart enough to know when it is processing the collection, so it will call your template for each element in the collection, generating the correct names in your form.

To do what you need, follow these steps:

  • Create the EditorTemplates folder inside the current current view folder (for example, if your view is Home\Index.cshtml , create the Home\EditorTemplates ). The name is important because it complies with the pattern search agreement.
  • Place a partial view in this folder. Alternatively, place it in the Shared\EditorTemplates .
  • Rename the partial view to Datapoint.cshtml (this is important because template names are based on type name convention).

Now the corresponding view code will look like this:

 // Note: I removed @ from Model here. @foreach (var g in Model) { <h2>@g.Name</h2> @Html.EditorFor(m => g.DataPoints) <hr /> } 

This ensures that your views are shared as you originally planned.

Update for comments

Well, as I mentioned below, the problem is that the model binder does not bind a DataPoint to the correct Group in any way. A simple fix is ​​to change the view code to this:

 for (int i = 0; i < Model.Count; i++) { <h2>@Model[i].Name</h2> @Html.EditorFor(m => m[i].DataPoints) <hr /> } 

This will generate the names correctly and should solve the model binding problem.

Op addendum

Following John’s answer, I also included the missing properties in the Group table as HiddenFor, in which game the model returned to my post.

 @for (int i = 0; i < Model.Count(); i++) { @Html.HiddenFor(t => Model[i].ID) @Html.HiddenFor(t => Model[i].BusinessUnitID) @Html.HiddenFor(t => Model[i].SortOrder) @Html.HiddenFor(t => Model[i].Name) <h2>@Model[i].Name</h2> @Html.EditorFor(m => Model[i].Datapoints) <hr /> } 

Update 2 - Cleaning Solution

My advice on using EditorTemplate for each DataPoint also applies to each Group . Instead of needing a for loop, relying on presentation logic again, you can completely avoid this by setting the EditorTemplate for Group . The same steps apply, as indicated above, in terms of placing the template.

In this case, the template will be Group.cshtml and will look like this:

 @model Jmp.StaticMeasures.Models.Group <h2>@Model.Name</h2> @Html.EditorFor(m => m.DataPoints) <hr /> 

As discussed above, this will invoke a template for each item in the collection that will also generate the correct indexes for each Group . Now your initial appearance can be simplified to:

 @model List<Jmp.StaticMeasures.Models.Group> @using (Html.BeginForm()) { // Other markup @Html.EditorForModel(); } 
+13
source share

Binder cannot be attached to the list of objects if it is returned in this way. Yes, partly your problem. You need to specify a number in your form for identifiers.

Do something like this:

  // pseudocode @model List<Jmp.StaticMeasures.Models.Group> <div class="row"> @using (Html.BeginForm()) { @Html.AntiForgeryToken() @Html.ValidationSummary(true) <div class="large-12"> <div class="large-8 large-centered columns panel"> for(int i; i<Model.Count; i++) { <h2>@g.Name</h2> @Html.HiddenFor(d => Model[i].Id) @Html.HiddenFor(d => Model[i].Name) @Html.HiddenFor(d => Model[i].SortOrder) @Html.DisplayTextFor(d => Model[i].Name) @Html.EditorFor(d => Model[i].StaticValue) @Html.ValidationMessageFor(d => Model[i].StaticValue) <hr /> } <input type="submit" class="button" value="Save Changes"/> </div> </div> } </div> 

Learn more about linking to a list on the Haack blog.

+1
source share

You get a null model because of how binding models collections.

Your partial view displays these inputs, for example:

 <input type="hidden" name="ID" value="1"/> ... 

And again, for each entry in your List<Group> . Unfortunately, the model’s middleware will not know how to handle this, and you will get a zero value.

What your inputs look like:

 <input type="hidden" name="groups[0].ID" value="1"/> ... <input type="hidden" name="groups[1].ID" value="2"/> 

There can be no break in numbering. One way to get this is to rewrite the way you use the Html.xxxFor methods, for example: iterate through the list and do this:

 @Html.HiddenFor(d => Model[i].Id) 

Here are two resources that explain this in detail and provide yet other examples of how to make model binding work with collections:

http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/

0
source share

All Articles