Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

List Model Binding in MVC

0.00/5 (No votes)
6 May 2014 1  
ASP.NET MVC List Model binding

Introduction

I am new to MVC as I just started working on a project using it. I was stuck on this model binding thingy for a couple of days, so just want to share this experience. Hope it will be useful to other newbies like me.

Background

I have this master-detail (one to many) relationship in one view and the detail is represented by HTML table in which user can add/remove the rows as they like. And I want the Add method in the controller to just accept the model as a parameter. Something like this:

I found this great article here which helped me a lot.

Using the Code

OK, just to make it simple, I will create 2 classes with master-detail relationship in the model class.

I name it AuthorModel:

 public class Author
    {
        public Author()
        {
            Books = new List<Book>();
        }
        public string Name { get; set; }
        public List<Book> Books { get; set; }
    }

    public class Book
    {
        public string Title { get; set; }
        public DateTime PublishedDate { get; set; }
    } 

Next, we add controller and view into the project:

Controller :

public class AuthorController : Controller
    {
        //
        // GET: /Author/

        public ActionResult Index()
        {
            var model = new Author();
            return View(model);
        }
      
    } 

View :

@using MvcModelBinding.Models
@model MvcModelBinding.Models.Author
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
    Index</h2>
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Author</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>
        <div>
            <input id="btnAddBook" type="button" 
            onclick="addRow();" value="Add Book" />
        </div>
        <table>
            <thead>
                <tr>
                    <td>
                        Title
                    </td>
                    <td>
                        Published Date
                    </td>
                </tr>
            </thead>
            <tbody id="tbBooks">
            </tbody>
        </table>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>  


Since the idea is we can add/remove the books in the client side, then only submit it to the server after we are satisfied. The client script looks like this:

<script language="javascript" type="text/javascript">
        function addRow() {

            @{
                Model.Books.Add(new Book());
            }

            var index = $("#tbBooks").children("tr").length;

            var indexCell = "<td style='display:none'><input name='Books.Index' 
            type='hidden' value='" + index + "' /></td>";
            var titleCell = "<td><input id='Books_" + index + "__Title' 
            name='Books[" + index + "].Title' type='text' value='' /></td>";
            var publishedCell = "<td><input id='Books_" + index + "__Title' 
            name='Books[" + index + "].PublishedDate' type='text' value='' /></td>";
            var removeCell = "<td><input id='btnAddBook' type='button' 
            value='Remove' onclick='removeRow(" + index + ");' /></td>";

            var newRow = "<tr id='trBook" + index + "'>" + 
            indexCell + titleCell + publishedCell + removeCell + "</tr>";
            $("#tbBooks").append(newRow);
        }

        function removeRow(id) {
            var controlToBeRemoved = "#trBook" + id;
            $(controlToBeRemoved).remove();
        }
    </script>

Note that the hidden value part below is necessary to allow arbitrary indices.

 var indexCell = "<td style='display:none'>
    <input name='Books.Index' type='hidden' value='" + index + "' /></td>";

If we didn't add this extra hidden input, removing n index in the collection will also remove the subsequent items; for example: if you have 3 items in the collection, removing item number 2 will also remove item number 3.

Now let us add code in the controller to display the result:

 [HttpPost]
 public string Index(Author author)
 {
            var sb = new StringBuilder();
            try
            {
                sb.AppendFormat("Author : {0}", author.Name);
                sb.AppendLine("<br />");
                sb.AppendLine("--------------------------------");
                sb.AppendLine("<br />");
                foreach (var book in author.Books)
                {
                    sb.AppendFormat("Title : {0} | Published Date : {1}", book.Title, book.PublishedDate);
                    sb.AppendLine("<br />");
                }
            }
            catch(Exception ex)
            {
                throw ex;
            }

            return sb.ToString();
 }

Observe that the controller only accepts Author model as the parameter.

Here are some of the results:

Delete the second item:

That's it! Hope it helps.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here