December 2017 : Online courses in ASP.NET MVC and Angular 4. Conducted by Bipin Joshi. Read more...
Registration for December 2017 batches of ASP.NET MVC / Core and Angular 4 online courses have already started. Conducted by Bipin Joshi. Book your seat today ! Click here for more details.

Model Binding to List of Objects in ASP.NET MVC

Showing a single record for editing is quite common and the default model binding of ASP.NET MVC takes care of mapping the form fields to the model properties. However, sometimes you need to edit multiple records. For example, you may want to display an editable grid to the end user filled with existing data. The user can edit the values from multiple rows and hit Save in an attempt to save the data. In this case multiple model objects are being submitted to the action method. The single record editing works on the assumption that form field names from the view match the corresponding model property names. However, when multiple model objects are submitted this assumption is no longer valid. Luckily, by tweaking the form field names you can get this to work as expected. Let's see how.

Begin by creating a new ASP.NET MVC Application. Then right click on the Models folder and add an ADO.NET entity framework data model to it. Configure the model to use Customers table of the Northwind database. The following figure shows this model:

Then add HomeController in the Controllers folder. Modify the default Index() action method as shown below:

public ActionResult Index()
{
  NorthwindEntities db=new NorthwindEntities();
  var query = from c in db.Customers
              where c.Country=="UK"
              orderby c.CustomerID
              select c;
  return View(query.ToList());
}

The Index() action method simply selects all the customers from UK and passes them to the Index view as a List of Customer entities.

Then right click on the Index() action method and add Index view. The Index view is where you need to follow certain naming convention to get the desired results:

@model List<ModelBindingToListDemo.Models.Customer>
...
    <h1>List of Customers</h1>
    @using (Html.BeginForm("Index", "Home", FormMethod.Post))
    {
        <table border="1" cellpadding="6">
            @for (int i = 0; i < Model.Count;i++ )
            { 
                <tr>
                    <td>@Html.TextBox("customers[" + @i + "].CustomerID", 
                                      Model[i].CustomerID, 
                                      new { @readonly = "readonly" })</td>
                    <td>@Html.TextBox("customers[" + @i + "].CompanyName", 
                                      Model[i].CompanyName)</td>
                    <td>@Html.TextBox("customers[" + @i + "].ContactName", 
                                      Model[i].ContactName)</td>
                    <td>@Html.TextBox("customers[" + @i + "].Country", 
                                      Model[i].Country)</td>
                </tr>
            }
            <tr>
                <td colspan="4">
                    <input type="submit" value="Submit" />
                </td>
            </tr>
        </table>
    }
...

Notice the markup shown in the bold letters. The code is basically generating names for the textboxes matching the following convention:

customers[n].<Model_Property_Name>

Where n is an index starting from 0 and Model_Property_Name is the name of the properties such as CustomerID, CompanyName, ContactName and Country. For the sake of simplicity the above code uses only four properties form the Customer model class. So, all the textboxes having same index are considered as "one record". This naming convention is required to successfully bind data to the model as you will see later.

The following figures shows how the view looks like in the browser:

To see how the textbox names are being generated view the HTML source in the browser.

The above <form> submits the data to Index() method using post method. To handle this data write the second version of Index() action method as follows:

[HttpPost]
public ActionResult Index(List<Customer> customers)
{
  NorthwindEntities db=new NorthwindEntities();
  foreach (Customer cust in customers)
  {
    Customer existing = db.Customers.Find(cust.CustomerID);
    existing.CompanyName = cust.CompanyName;
    existing.ContactName = cust.ContactName;
    existing.Country = cust.Country;
  }
  db.SaveChanges();
  return View();
}

The overloaded Index() method takes a parameter - List of Customer entities. Recollect that this parameter name - customers - is what you used in the view markup earlier. Due the naming conventions followed the model binding framework of ASP.NET MVC transforms the form field values into a generic List of Customer objects. Once received you simply iterate through the List and modify the existing Customer with the new one. You can also put some logic to detect whether a record was really changed or not. Once all the rows are modified SaveChanges() is called to save the changes.

As mentioned earlier the naming convention requires that the index start at 0 and then sequentially increment for each record. If you try changing the start index to say 10, the model binding will fail to bind the data. What if you don't want to start the index from 0? For example, imagine a case where you are removing some row using client side script. In such cases the there might be "gaps" in between various index values. To overcome this situation you can follow an alternate naming convention:

@using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
  <table border="1" cellpadding="6">
  @for (int i = 0; i < Model.Count;i++ )
  { 
    <tr>
      <td>
      @Html.Hidden("customers.Index", (@i + 10))
      @Html.TextBox("customers[" + (@i + 10) + "].CustomerID", 
                    Model[i].CustomerID, new { @readonly = "readonly" })
      </td>
      <td>@Html.TextBox("customers[" + (@i + 10) + "].CompanyName", 
                        Model[i].CompanyName)</td>
      <td>@Html.TextBox("customers[" + (@i + 10) + "].ContactName", 
                        Model[i].ContactName)</td>
      <td>@Html.TextBox("customers[" + (@i + 10) + "].Country", 
                        Model[i].Country)</td>
    </tr>
  }
<tr>
...
}

Notice the above markup carefully. Each table row now has a hidden form field. The name of the hidden form field is customers.Index and its value is set to some arbitrary index (i + 10 in this case). Then all the textboxes are assigned names of the form:

 customers[<arbitrary_index>].<model_property_name>

In this case all the textboxes having same index as specified by the hidden field are considered as "one record". In this case the index need not be a number. It can be a string also. Again, recollect that "customers" in the above markup is the name of the parameter of the Index() method.

That's it! Run the application and test if it works as expected.


Bipin Joshi is a software consultant, an author and a yoga mentor having 22+ years of experience in software development. He also conducts online courses in ASP.NET MVC / Core and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Meditation and Mindfulness to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 18 February 2014


Tags : ASP.NET Data Access MVC