Click here to Skip to main content
15,886,026 members
Articles / Web Development / ASP.NET

Collection Binding in ASP.NET MVC3 with AJAX

Rate me:
Please Sign up or sign in to vote.
4.67/5 (3 votes)
30 Nov 20112 min read 28.8K   15  
Collection Binding in ASP.NET MVC3 with AJAX

There is a less-common scenario in web applications where we need to edit collection of objects and submit the whole back to the system. For example, let us take the below view model:

C#
public class FruitModel...
        public string Name { get; set; }
        public bool IsFresh { get; set; }
        public bool IsPacked { get; set; }
		public decimal UnitPrice { get; set; }

The UI for this scenario is shown below:

Leave the top and bottom “Lorem ipsum” text, these are just gap fillers. The user can change the “IsFresh” and “IsPacked” settings of the fruits and the unit prices.

Challenge

This post addresses the following simple problems when using ASP.NET MVC3:

  • Sending back collection of data to an MVC action
  • Also send back additional parameter(s) to the same MVC action
  • Sending back read-only data
  • By Ajax

Solution

When the user hits this site, the HomeController’s Index will be called:

C#
public ActionResult Index()...
	List<FruitModel> collection = new List<FruitModel>()
	{
		new FruitModel {Name = "Apple", IsFresh=true, 
				IsPacked=false, UnitPrice = 10M},
		new FruitModel {Name = "Orange", IsFresh=false, 
				IsPacked=false, UnitPrice = 5M},
		new FruitModel {Name = "Strawberry", IsFresh=true, 
				IsPacked=true, UnitPrice = 15M}
	};
	ViewBag.NetAmount = IncludeTax(collection.Sum(fm => fm.UnitPrice));
	ViewBag.ShopId = Guid.NewGuid();
	return View(collection);

In the Index view, I’ve used NetAmount value of ViewBag as shown below:

HTML
</div>
<div>
<pre><h2>Welcome to Fruit Shop</h2>
<div>Lorem ipsum... </div>
<div>
	@Html.Partial("_Fruit", (List<MvcApplication1.Models.FruitModel>)Model)
</div>
<div id="netAmountDiv" name="netAmountDiv" style="color:Blue">
	Net Amount: @ViewBag.NetAmount
</div>
<div>Lorem ipsum...</div>

The main part of the Fruit Shop is defined in _Fruit partial view.  It requires the FruitModel collection and shop ID (in ViewBag).

Simply passing the Model in @Html.Partial(…) will throw the error “‘System.Web.Mvc.HtmlHelper<dynamic>’ has no applicable method named ‘Partial’ but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.”.  So, cast it to the appropriate type, here List<MvcApplication1.Models.FruitModel>.

The partial view _Fruit is:

HTML
</div>
<div>
<pre>@model List<MvcApplication1.Models.FruitModel>

@using (Ajax.BeginForm(new AjaxOptions
        {
            HttpMethod = "Post",
            UpdateTargetId = "netAmountDiv"
        }
))
{

<table>
    <tr>
        <th>
            Name
        </th>
        <th>
            IsFresh
        </th>
        <th>
            IsPacked
        </th>
        <th>Unit Price</th>
    </tr>

@for (int i = 0; i < Model.Count; i++)
{
    <tr>
        <td>
            @Html.DisplayFor(modelItem => Model[i].Name)
            @Html.HiddenFor(modelItem => Model[i].Name)
        </td>
        <td>
            @Html.EditorFor(modelItem => Model[i].IsFresh)
        </td>
        <td>
            @Html.EditorFor(modelItem => Model[i].IsPacked)
        </td>
        <td>
            @Html.EditorFor(modelItem => Model[i].UnitPrice)
        </td>
    </tr>
}

</table>
        <input type="hidden" id="shopId" name="shopId" value="@ViewBag.ShopId" />
        <div>
		    <input name="submitFruit" type="submit" value="Change" />
	    </div>
}

Now the important point here is, when you want to post back collection of FruitModel, the naming pattern of every HTML item in the collection should be “obj-name[index].property-name”.  For example, for the above code, ASP.NET generates HTML for an item like below:

HTML
</div>
<div>
<pre><td>
	Apple
	<input name="[0].Name" type="hidden" value="Apple" />
</td>
<td>
	<input checked="checked" class="check-box" data-val="true" 
		data-val-required="The IsFresh field is required." 
		name="[0].IsFresh" type="checkbox" value="true" />
	<input name="[0].IsFresh" type="hidden" value="false" />
</td>
<td>
	<input class="check-box" data-val="true" 
		data-val-required="The IsPacked field is required." name="[0].IsPacked" 
		type="checkbox" value="true" /><input name="[0].IsPacked" 
		type="hidden" value="false" />
</td>
<td>
	<input class="text-box single-line" data-val="true" 
		data-val-number="The field UnitPrice must be a number." 
		data-val-required="The UnitPrice field is required." 
		name="[0].UnitPrice" type="text" value="10.00" />
</td></pre>
</div>
<div>

This HTML code actually generates a post back collection as shown below when submitting the form.

submitFruit=Change&[0].Name=Apple&[0].IsFresh=true&[0].IsFresh=false&[0].IsPacked=false&
[0].UnitPrice=10.00&[1].Name=Orange&[1].IsFresh=false&[1].
	IsPacked=true&[1].IsPacked=false&
[1].UnitPrice=5.00&[2].Name=Strawberry&[2].IsFresh=true&[2].
	IsFresh=false&[2].IsPacked=true&
[2].IsPacked=false&[2].
 UnitPrice=25&shopId=c9517c6b-c911-4a28-9a0a-3e47ccb60bd8&X-Requested-With=XMLHttpRequest

The above data matched with List<FruitModel> model and with the other parameter name too. The additional parameter I’m passing is “shopId” hidden value which is received from ViewBag.ShopId. The main changes I made in the above code are:

  • Used List<T> for @model instead of IEnumerable<T>, hence I can use Count property.
  • Used for i = 0…List<T>.Count instead of foreach.

ASP.NET MVC3 uses “name.propertyname” pattern, if you use “foreach”.  This wouldn’t send back the collection to the server.  Now, let us see the Index action for POST:

HTML
</div>
<div>
<pre>[HttpPost]
public ActionResult Index(Guid shopId, List<FruitModel> collection)...
	decimal addlTax = 0M;
	if (collection.Any(fm => fm.UnitPrice > 200)) addlTax += 2M;
	return Content("Net Amount: " + IncludeTax(collection.Sum
		(fm => fm.UnitPrice) + addlTax).ToString());</pre>
</div>
<div>

Leave the tax calculation stuff, it is just for making some difference from GET Index(). The above method sends back the tax calculation as plain text to the client. This is the place for AJAX. This can be achieved by Ajax.BeginForm() in the above code, where I’ve mentioned that the result should be placed on an element with id “netAmountDiv”. So, we can get the result asynchronously. To make this AJAX.BeginForm() to work, you have to:

  • include jQuery’s unobtrusive AJAX script (jquery.unobtrusive-AJAX.min.js)
  • add “<add key=”UnobtrusiveJavaScriptEnabled” value=”true” />” option in appsetting section of web.config

Also, note that to send read-only item as part of the collection, in the above example FruitModel.Name, use hidden input control also.

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


Written By
Architect Aditi
India India
Working as Architect for Aditi, Chennai, India.

My Website: www.udooz.net

My Blog: www.udooz.net/blog

Comments and Discussions

 
-- There are no messages in this forum --