65.9K
CodeProject is changing. Read more.
Home

List Model Binding in MVC

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (13 votes)

Apr 29, 2014

CPOL

1 min read

viewsIcon

132223

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.