Incorrect List Model Binding Indices When Using HTML Helpers

Incorrect List Model Binding indices when using HTML Helpers

The reason for this behavior is that the HtmlHelper methods use the value from ModelState (if one exists) to set the value attribute rather that the actual model value. The reason for this behavior is explained in the answer to TextBoxFor displaying initial value, not the value updated from code.

In your case, when you submit, the following values are added to ModelState

Cars[1].Make: Land Rover 2
Cars[2].Make: Audi 3
Cars[3].Make: Honda 4

Note that there is no value for Cars[0].Make because you deleted the first item in the view.

When you return the view, the collection now contains

Cars[0].Make: Land Rover 2
Cars[1].Make: Audi 3
Cars[2].Make: Honda 4

So in the first iteration of the loop, the TextBoxFor() method checks ModelState for a match, does not find one, and generates value="Land Rover 2" (i.e. the model value) and your manual input also reads the model value and sets value="Land Rover 2"

In the second iteration, the TextBoxFor() does find a match for Cars[1]Make in ModelState so it sets value="Land Rover 2" and manual inputs reads the model value and sets value="Audi 3".

I'm assuming this question is just to explain the behavior (in reality, you would save the data and then redirect to the GET method to display the new list), but you can generate the correct output when you return the view by calling ModelState.Clear() which will clear all ModelState values so that the TextBoxFor() generates the value attribute based on the model value.

Side note:You view contains a lot of bad practice, including polluting your markup with behavior (use Unobtrusive JavaScript), creating label element that do not behave as labels (clicking on them will not set focus to the associated control), unnecessary use of <br/> elements (use css to style your elements with margins etc) and unnecessary use of new { @id = "car-make-" + i }. The code in your loop can be

@for (int i = 0; i < Model.Cars.Count; i++)
{
<div class="form-group row">
<hr />
@Html.LabelFor(m => m.Cars[i].Make, "Make (@i)")
@Html.TextBoxFor(m => m.Cars[i].Make, new { @class = "form-control" })
....
<input type="hidden" name="Cars.Index" value="@i" />
<button type="button" class="btn btn-sm btn-danger delete">Delete Entry</button>
</div>
}

$('.delete').click(function() {
$(this).closest('.form-group').remove();
}

nested list model binding and HTML helpers

The problem is coming from using an editor template for the list. Even though you didn't post it, I'm assuming in your main view you have something like:

<%: Html.EditorFor(m => m._products, "ProductListTemplate") %>

This results in a prefix being added of _products. Then in your list template, the field names there are being bound in the form of [0].ProductPK. So the result is _products.[0].ProductPK.

Instead in your main view you should be doing:

<fieldset>
<legend>REQUEST FOR: (select item(s) to display access form)</legend>
<table>
<tr>
<% for (int i = 0; i < Model.Count(); i++) { %>
<td>
<%: Html.EditorFor(model => model._products[i], "ProductCheckBox") %>
</td>
<% if (i+1 % 5 == 0) { %>
</tr>
<tr>
<% } %>
<% } %>
</tr>
</table>
</fieldset>

In other words, remove that middle layer that's screwing up the field names. Since the expression being passed here model._products[i], your field names will be prefixed with _products[0].

If your goal with the list template was simply to reuse this portion of the view, you can still do that but the model you pass into should be your EAF class, and then you'd use Html.Partial instead of Html.EditorFor:

<%: Html.Partial("ProductListTemplate") %>

ProductListTemplate.cshtml

@model Namespace.To.EAF

...

<%: Html.EditorFor(model => model._products[i], "ProductCheckBox") %>

List Binding with model data

Its not clear how you are generating those inputs, but the name attributes are incorrect. You model does not contain a collection property named h, but it does contain one named TransfersDetail, so your inputs need to be

<input name="TransfersDetail.Index" type="hidden" value="c3a3f7dd-41bb-4b95-b2a6-ab5125868adb">
<input name="TransfersDetail[c3a3f7dd-41bb-4b95-b2a6-ab5125868adb].detToolCode" type="hidden" value="1234">

Its also not clear why your adding an id attribute (if you referencing collection items in jQuery, you would be better off using class names and relative selectors), but the id your using does not have an indexer suggesting that your going to be generating duplicate id attributes which is invalid html (and jQuery selectors would not work in any case)

Submitting a list/Collection with non-sequential indices/positions

I am posting this answer for future reference for anyone else who is having same doubts.

So if you have a collection like this:

public class Book {
public string Title { get; set; }
public string Author { get; set; }
public DateTime DatePublished { get; set; }
}

//Action method on HomeController
public ActionResult UpdateProducts(ICollection<Book> books) {
return View(books);
}

And your form after adding 3 books looks like this:

<form method="post" action="/Home/Create">

<input type="text" name="[0].Title" value="Curious George" />
<input type="text" name="[0].Author" value="H.A. Rey" />
<input type="text" name="[0].DatePublished" value="2/23/1973" />

<input type="text" name="[1].Title" value="Code Complete" />
<input type="text" name="[1].Author" value="Steve McConnell" />
<input type="text" name="[1].DatePublished" value="6/9/2004" />

<input type="text" name="[2].Title" value="The Two Towers" />
<input type="text" name="[2].Author" value="JRR Tolkien" />
<input type="text" name="[2].DatePublished" value="6/1/2005" />

<input type="submit" />
</form>

Now If you guys want to add delete functionality such that your form might contain non-sequential entries, then something like this can be done:

<form method="post" action="/Home/Create">

<input type="hidden" name="products.Index" value="cold" />
<input type="text" name="products[cold].Name" value="Beer" />
<input type="text" name="products[cold].Price" value="7.32" />

<input type="hidden" name="products.Index" value="123" />
<input type="text" name="products[123].Name" value="Chips" />
<input type="text" name="products[123].Price" value="2.23" />

<input type="hidden" name="products.Index" value="caliente" />
<input type="text" name="products[caliente].Name" value="Salsa" />
<input type="text" name="products[caliente].Price" value="1.23" />

<input type="submit" />
</form>

In the example above, we provide a hidden input with the .Index suffix for each item we need to bind to the list. This will give the model binder a nice collection of indices to look for when binding to the list.

No post values when using html helpers

I believe your problem is the same as here, so you need to add a prefix:

[HttpPost]
public ActionResult SendReply([Bind(Prefix="SendReplyPmForm")]SendReplyPmForm SendReplyForm)
{
...
}

Model Binding to a List in asp.net mvc 2. Found example. Can't find where I'm going wrong

No need of for loops. All that you need is:

<%= Html.EditorFor(m => m.Assessments) %>

This will automatically call your AssessmentItem.ascx editor template for each item in the Assessments collection of your model. So just make sure the controller initializes it to some value. It will automatically take care of generating correct names for the inputs in the editor template so that the model binding works.

<%: ... %> is an ASP.NET 4.0 only and is equivalent to <%= Html.Encode(...) %> but if you are using .NET 3.5 it won't be available to you. Notice also the absence of ; at the end of the EditorFor helper when used with <%= ... %>.

ASP.NET MVC2 -- Trouble with Model Binding and Html.Textbox()

I figured out the answer to my own question when I stumbled upon another variable that needed to be reset. As I was looking at the data structure, I realized what I wanted was the pristine state where there were no Keys in the ModelState.

        ModelState.Remove(key);

Where "key" is the value you're trying to reset.

MVC3 ModelBinding to a collection posted back with index gaps

There are some very good blog posts that allow you to modelbind to a list without the need to provide zero based contiguous index. plz have a look at
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

http://zahidadeel.blogspot.com/2011/05/master-detail-form-in-aspnet-mvc-3-ii.html

Furthermore, if you are interested in MVVM pattern and knockout js you can check this great work by steve sanderson

For more reading put "editing varibale length list mvc style" in google and it will give u a dozen useful links



Related Topics



Leave a reply



Submit