Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does MVC 4 List Model Binding work?

If I want a set of inputs in a form to bind to a 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 am curious about how forgiving the model binder is. 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 would the model binder handle this? Would it bind to a List of length 9 with nulls? Or would it still bind to a List of length 3? Or would it choke altogether?

Why I care

I want to implement a dynamic form in which the user may add rows to the form, and also may delete rows from the form. So if I a user deletes row 2 out of 8 total rows, I want to know if I'll need to renumber all of the subsequent inputs.

like image 925
Eric Avatar asked Feb 11 '13 23:02

Eric


People also ask

How does MVC model binding work?

Model binding is a mechanism ASP.NET MVC uses to create parameter objects defined in controller action methods. The parameters can be of any type, from simple to complex ones. It simplifies working with data sent by the browser because data is automatically assigned to the specified model.

Does MVC use data binding?

MVC doesn't use data bindings like old web api. You have to use model bindings in a MVC or MVVM approach.

How does model binding work in asp net core?

Model binding allows controller actions to work directly with model types (passed in as method arguments), rather than HTTP requests. Mapping between incoming request data and application models is handled by model binders.


2 Answers

There is a specific wire format for use with collections. This is discussed on Scott Hanselman's blog here:

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

Another blog entry from Phil Haack talks about this here:

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

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

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

like image 52
Erik Funkenbusch Avatar answered Oct 16 '22 19:10

Erik Funkenbusch


I followed this approach linked in the blogs above and added a few details that might be helpful to some - especially as I wanted to dynamically add any number of rows but did not want to use AJAX to do so (I wanted the form to only submit in the post). I also did not want to worry about maintaining sequential ids. I was capturing a list of start and end dates:

View 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 in 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 the blog post linked in the @ErikTheVikings post above describe, the collection is created by the repeated hidden element: @Html.Hidden("Dates.Index", d.DateID) for each entry in the collection on the page.

I wanted to arbitrarily add rows without using AJAX to post data back to the server which I did by creating a hidden div containing a template of one "row" / item in the collection:

Hidden "Template" row:

<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 used jQuery which clones the template, provides a random id to use for a new row and appends the now visible cloned row 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> 

JSFiddle example of the result: http://jsfiddle.net/mdares/7JZh4/

like image 38
Matthew Avatar answered Oct 16 '22 18:10

Matthew