How does MVC 4 List Binding work?

If I need a set of form inputs to bind to List in MVC 4, I know that the following naming convention for input name attributes will work:

 <input name="[0].Id" type="text" /> <input name="[1].Id" type="text" /> <input name="[2].Id" type="text" /> 

But I'm curious how the model binder is forgiven. For example, what about the following:

 <input name="[0].Id" type="text" /> <input name="[3].Id" type="text" /> <input name="[8].Id" type="text" /> 

How to deal with this model linking device? Will it be tied to a List length 9 with zeros? Or will it be tied to a List length 3? Or will it suffocate at all?

Why do i care

I want to implement a dynamic form in which the user can add lines to the form, and can also delete lines from the form. Therefore, if I delete line 2 of 8 common lines, I want to know if I need to renumber all subsequent inputs.

+53
c # asp.net-mvc asp.net-mvc-4 model-binding
Feb 11 '13 at
source share
5 answers

There is a specific wiring format for use with collections. This is stated in the blog of Scott Hanselman:

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

Another blog post by Phil Haack talks about it here:

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

Finally, a blog post that does exactly what you want here:

http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

+44
Feb 12 '13 at 4:51
source share

I followed this approach related in the blogs above and added some details that may be useful for some - especially since I wanted to dynamically add any number of lines, but did not want to use AJAX for this (I only need to submit the form in the mail) . I also did not want to worry about storing consecutive identifiers. I wrote down a list of start and end dates:

Show model:

 public class WhenViewModel : BaseViewModel { public List<DateViewModel> Dates { get; set; } //... Other properties } 

Start / End Date View Model:

 public class DateViewModel { public string DateID { get; set; } public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } } 

Then using them on the page (with datepicker):

 <div class="grid-8-12 clear" id="DatesBlock"> @{ foreach (DateViewModel d in Model.Dates) { @:<div class="grid-5-12 left clear"> @Html.Hidden("Dates.Index", d.DateID) @Html.Hidden("Dates[" + d.DateID + "].DateID", d.DateID) //ID again to populate the view model @Html.TextBox("Dates[" + d.DateID + "].StartDate", d.StartDate.Value.ToString("yyyy-MM-dd")) @:</div> @:<div class="grid-5-12"> @Html.TextBox("Dates[" + d.DateID + "].EndDate", d.EndDate.Value.ToString("yyyy-MM-dd")) @:</div> <script type="text/javascript"> $('input[name="Dates[@d.DateID].StartDate"]') .datepicker({ dateFormat: 'yy-mm-dd'}); $('input[name="Dates[@d.DateID].EndDate"]') .datepicker({dateFormat: 'yy-mm-dd'}); </script> } } </div> <a href="#" onclick="AddDatesRow()">Add Dates</a> 

As described in the blog post described earlier in the @ErikTheVikings article, the collection is created by a repeating hidden element: @Html.Hidden("Dates.Index", d.DateID) for each entry in the collection on the page.

I wanted to add rows arbitrarily without using AJAX to send the data back to the server, which I did by creating a hidden div containing the template of one β€œrow” / element in the collection:

Hidden line "Template":

 <div id="RowTemplate" style="display: none"> <div class="grid-5-12 clear"> @Html.Hidden("Dates.Index", "REPLACE_ID") @Html.Hidden("Dates[REPLACE_ID].DateID", "REPLACE_ID") @Html.TextBox("Dates[REPLACE_ID].StartDate", "") </div> <div class="grid-5-12"> @Html.TextBox("Dates[REPLACE_ID].EndDate", "") </div> </div> 

Then, jQuery is used, which clones the template, provides a random identifier to use for the new line, and now adds the visible cloned line to the containing div above:

jQuery to complete the process:

 <script type="text/javascript"> function AddDatesRow() { var tempIndex = Math.random().toString(36).substr(2, 5); var template = $('#RowTemplate'); var insertRow = template.clone(false); insertRow.find('input').each(function(){ //Run replace on each input this.id = this.id.replace('REPLACE_ID', tempIndex); this.name = this.name.replace('REPLACE_ID', tempIndex); this.value = this.value.replace('REPLACE_ID', tempIndex); }); insertRow.show(); $('#DatesBlock').append(insertRow.contents()); //Attach datepicker to new elements $('input[name="Dates['+tempIndex+'].StartDate"]') .datepicker({dateFormat: 'yy-mm-dd' }); $('input[name="Dates['+tempIndex+'].EndDate"]') .datepicker({dateFormat: 'yy-mm-dd' }); } </script> 

Example JSFiddle result: http://jsfiddle.net/mdares/7JZh4/

+15
Feb 06 '14 at 16:37
source share

I have a dynamic list that looks like this:

 <ul id="okvedList" class="unstyled span8 editableList"> <li> <input data-val="true" data-val-required="The Guid field is required." id="Okveds_0__Guid" name="Okveds[0].Guid" type="hidden" value="2627d99a-1fcd-438e-8109-5705dd0ac7bb"> --//-- </li> 

so when i add or delete a row (li element) i have to reorder the elements

  this.reorderItems = function () { var li = this.el_list.find('li'); for (var i = 0; i < li.length; i++) { var inputs = $(li[i]).find('input'); $.each(inputs, function () { var input = $(this); var name = input.attr('name'); input.attr('name', name.replace(new RegExp("\\[.*\\]", 'gi'), '[' + i + ']')); var id = input.attr('id'); input.attr('id', id.replace(new RegExp('_.*__', 'i'), '_' + i + '__')); }); } }; 

this list is placed in a simple Html.BeginFrom from clientide and as a List in action parameter on serveride

+3
Feb 12 '13 at 4:35
source share

I also encountered a similar problem in the past, and I use KnockoutJS to handle such a scenario.

Basically, the knockout sends the collection to a JSON string, and I will deserialize them in my controller.

For more information: http://learn.knockoutjs.com/#/?tutorial=collections

+1
Feb 12 '13 at 4:43
source share

I'm having a bit of trouble when I use the Chrome browser and click the "Back" button, and I find the input with type = "hidden" when the dynamically set values ​​are not processed properly by the Chrome browser.

maybe we can change

 <input type="hidden" name="Detes.Index" value="2016/01/06" /> 

to

 <div style="display: none"> <input type="text" name="Detes.Index" value="2016/01/06" /> </div> 

The form is more information: Chrome does not cache hidden form field values ​​for use in browser history http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/

0
Jan 06 '16 at 2:52 on
source share



All Articles