Thursday, March 1, 2012

Posting and binding generic list or array of complex object in MVC 3 using jQuery ajax

This post is an extension of the previous post. Please go through the previous post before proceeding with this. In this post we are going to discuss about posting a generic list or array of complex object from jQuery ajax.

Before proceeding with the solution, lets see how the posting of list works in case of a normal form post. To start with lets have the following model, view and controller actions-
    public class UserModel
    {
        public int UserId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    }
@model List<razor.Models.UserModel>
           
@using (Html.BeginForm())
{
    for (int i = 0; i < Model.Count; i++)
    {
        <div class="data">
        <h4>User ID: @Model[i].UserId</h4>
        First Name: @Html.TextBoxFor(m => m[i].FirstName)
        Last Name: @Html.TextBoxFor(m => m[i].LastName)
        Age: @Html.TextBoxFor(m => m[i].Age)
        <br /><br />
        </div> 
    }
    <input type="submit" value="Submit data" />
}

I think the above model and view are self explanatory.
        public ActionResult Binding_posting_to_generic_collection_of_custom_type_using_ajax()
        {
            List<UserModel> users = new List<UserModel>();
            for (int i = 0; i < 5; i++)
            {
                users.Add(new UserModel() {UserId=i, FirstName = "FirstName " + i.ToString(), LastName = "LastName " + i.ToString(), Age = i +34});
            }
            return PartialView(users);
        }
        [HttpPost]
        public ActionResult Binding_posting_to_generic_collection_of_custom_type_using_ajax(List<UserModel> userList)
        {
            return PartialView("Success");
        }
In case of controller action, the first action is used to create some in-memory user object and render the view. The second action method is used to accept the posted data from the view.

Now if we study the generated HTML by the view engine, we can see the pattern of HTML generation for the name attribute of the text boxes. Each property of a user object in the list got the name as [index].PropertyName. As we are binding to a collection, the default model binder will search for the values for the properties of the User class that are prefixed by an index. That's why the view engine is generating the name attribute of that fashion.

So, to pass the data using jQuery ajax we need to pass the querystring parameter of that fashion for model binder. So, first thing that can come to mind we will use traditional:true in the ajax call like in the previous post and ajax() method will internally use param() to build the querystring format for us. But as I have studied, jquery 1.7 version does not support for getting querystring format for array of complex object. That is if we execute the following code-
unescape($.param(
 [{FirstName:"FN 1", LastName:"LN 1", Age:35},
 {FirstName:"FN 2", LastName:"LN 2", Age:36},
 {FirstName:"FN 3", LastName:"LN 3", Age:37}]
 ))
we get the following result-
undefined=undefined&undefined=undefined&undefined=undefined
So, what we can do is write a little jQuery code and also use traditional:true of the ajax call to achieve this. The final jQuery code for doing this as below-
@model List<razor.Models.UserModel>
@for (int i = 0; i < Model.Count; i++)
{
    <div class="data">
        <h4>
            User ID: @Model[i].UserId</h4>
        First Name: @Html.TextBoxFor(m => m[i].FirstName)
        Last Name: @Html.TextBoxFor(m => m[i].LastName)
        Age: @Html.TextBoxFor(m => m[i].Age)
    </div> 
}
<input type="button" id="submitData" value="Submit data" />
<script type="text/javascript">
    $(document).ready(function () {
        $("#submitData").click(function () {
            var datatopost=new Object();
            $(".data").each(function (i, item) {
                datatopost["[" + i + "].FirstName"] = $(item).find("input[name*=FirstName]").val();
                datatopost["[" + i + "].LastName"] = $(item).find("input[name*=LastName]").val();
                datatopost["[" + i + "].Age"] = $(item).find("input[name*=Age]").val();
            });
            $.ajax({
                url: '@Url.Action("Binding_posting_to_generic_collection_of_custom_type_using_ajax")',
                type: 'POST',
                traditional: true,
                data: datatopost,
                dataType: "json",
                success: function (response) {
                    alert(response);
                },
                error: function (xhr) {
                    alert(xhr);
                }
            });
        });
    });
</script>
What we are doing here is looping through all the data and manually building a JavaScript object datatopost with properties like-
"[index].FirstName"
"[index].LastName"
"[index].Age"
So the actual assignment will be something like-
datatopost["[0].FirstName"]="First Name 0"
datatopost["[0].LastName"]="Last Name 0"
datatopost["[0].Age"]="35"

datatopost["[1].FirstName"]="First Name 1"
datatopost["[1].LastName"]="Last Name 1"
datatopost["[1].Age"]="36"

and so on...
Now we have our JavaScript object datatopost is ready, which is a plain JavaScript object with some properties. And now we can use traditional:true to automate the query string format.

Now we will be able to successfully post data from jQuery ajax. We can also see the data posted during the ajax request in the image below-


Note:
We can also change the logic of creation of datatopost like below-
$(".data").each(function (i, item) {
   datatopost[$(item).find("input[name*=FirstName]").attr("name")] = $(item).find("input[name*=FirstName]").val();
   datatopost[$(item).find("input[name*=LastName]").attr("name")] = $(item).find("input[name*=LastName]").val();
   datatopost[$(item).find("input[name*=Age]").attr("name")] = $(item).find("input[name*=Age]").val();
});

1 comment: