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
How to Implement Generic Repository Design Pattern with Dapper
Style Bundling Not Working After Iis Deployment (MVC 4)
Operators as Method Parameters in C#
How to Find a Java to C# Converter
Are Static Methods Thread Safe
Performance Tests of Serializations Used by Wcf Bindings
Compare Two Datatables to Determine Rows in One But Not the Other
How to Serialize/Deserialize a Dictionary with Custom Keys Using JSON.Net
Sqldataadapter VS SQLdatareader
Environment.Tickcount VS Datetime.Now
ASP.NET - Problems with Static Selected Style for a Selected Page on the Menu
Java Equivalents of C# String.Format() and String.Join()
How Does MVC 4 List Model Binding Work
How to Use the Iequalitycomparer