Editing a variable length list, ASP.NET MVC 2-style
MVC, UI, jQuery January 28th, 2010A while back I posted about a way of editing a list of items where the user can add or remove as many items as they want. Tim Scott later provided some helpers to make the code neater. Now, I find myself making use of this technique so often that I thought it would be worthwhile providing an update to show how you can do it even more easily with ASP.NET MVC 2 because of its strongly-typed and templated input helpers.
Download the demo project or read on for details.
Update (Feb 11, 2010):Thanks to Ryan Rivest for pointing out a bug in the original code and providing a neat fix. Code updated.
Getting Started
For this example I’m going to go with the same theme as last time and build a gift list editor. Our model object can simply be the following:
public class Gift { public string Name { get; set; } public double Price { get; set; } }
Displaying the Initial UI
To display the initial data entry screen, add an action method that renders a view and passes some initial collection of Gift instances.
public ActionResult Index() { var initialData = new[] { new Gift { Name = "Tall Hat", Price = 39.95 }, new Gift { Name = "Long Cloak", Price = 120.00 }, }; return View(initialData); }
Next, add a view for this action, and make it strongly-typed with a model class of IEnumerable<Gift>. We’ll create an HTML form, and for each gift in the collection, we’ll render a partial to display an editor for that gift.
<h2>Gift List</h2> What do you want for your birthday? <% using(Html.BeginForm()) { %> <div id="editorRows"> <% foreach (var item in Model) Html.RenderPartial("GiftEditorRow", item); %> </div> <input type="submit" value="Finished" /> <% } %>
I know it would be possible to use ASP.NET MVC 2’s Html.EditorFor() or Html.EditorForModel() helpers, but later on we’re going to need more control over the HTML field ID prefixes so in this example it turns out to be easier just to use Html.RenderPartial().
Next, to display the editor for each gift in the sequence, add a new partial view at /Views/controllerName/GiftEditorRow.ascx, strongly-typed with a model class of Gift, containing:
<div class="editorRow"> <% using(Html.BeginCollectionItem("gifts")) { %> Item: <%= Html.TextBoxFor(x => x.Name) %> Value: $<%= Html.TextBoxFor(x => x.Price, new { size = 4 }) %> <% } %> </div>
Here’s where we get to start using ASP.NET MVC 2 features and make things slightly easier than before. Notice that I’m using strongly-typed input helpers (Html.TextBoxFor()) to avoid the need to build element IDs manually. These helpers are smart enough to detect the “template context” in which they are being rendered, and use any field ID prefix associated with that template context.
You might also be wondering what Html.BeginCollectionItem() is. It’s a HTML helper I made that you can use when rendering a sequence of items that should later be model bound to a single collection. You give it some name for your collection, and it opens a new template context for that collection name, plus a random unique field ID prefix. It also automatically renders a hidden field, which in this case is called gifts.index, populating it with that unique ID, so when you later model bind to a list, ASP.NET MVC 2 will know that all the fields in this context should be associated with a single .NET object.
And now, if you visit the Index() action, you should see the editor as shown below. (I’ve added some CSS styles, obviously.)
![]()
Receiving the Form Post
To receive the data posted by the user, add a new action method as follows.
[HttpPost] public ActionResult Index(IEnumerable<Gift> gifts) { // To do: do whatever you want with the data }
How easy is that? Because Html.BeginCollectionItem() observes ASP.NET MVC 2 model binding conventions, you can receive all the items in the list without having to do anything funky.
You could also achieve the same with less code by using the built-in Html.EditorFor() or Html.EditorForModel() helpers, but because these use a different indexing convention (an ascending sequence, not a set of random unique keys), things would get more difficult when you try to add or remove items dynamically.
Dynamically Adding Items
If the user wants to add another item, they’ll need something to click to say so. Let’s add an “Add another…” link. Add the following to the main view, just before the “Finished” button.
<%= Html.ActionLink("Add another...", "BlankEditorRow", null, new { id = "addItem" }) %>
This is a link to an action called BlankEditorRow which doesn’t exist yet. The idea is that the BlankEditorRow action will return the HTML markup for a new blank row. We can fetch this markup via Ajax and dynamically append it into the page.
To make this Ajax call and append the result into the page, make sure you’ve got jQuery referenced, and create a click handler similar to this:
$("#addItem").click(function() { $.ajax({ url: this.href, cache: false, success: function(html) { $("#editorRows").append(html); } }); return false; });
Note that it’s very important to tell jQuery to tell the browser not to re-use cached responses, otherwise those unique IDs won’t always be so unique… And before we forget, we’ll need to put the BlankEditorRow action in place:
public ViewResult BlankEditorRow() { return View("GiftEditorRow", new Gift()); }
As you can see, it simply renders the same editor partial, passing a blank Gift object to represent the initial state. I’m very pleased that you can re-use the same editor partial in this way – it means there’s no duplication of view markup and we can stay totally DRY.
And that, in fact, is all you have to do – each time the user clicks “Add another…”, the client-side code will inject a new blank row into the editor. Because each row has its own unique ID, when the user later posts the form, all the data will be model bound into a single IEnumerable<Gift>.
Dynamically Removing Items
Removing items is easier, because all you have to do is remove the corresponding DOM elements from the HTML document. If the elements are gone, their contents won’t be posted to the server, so they won’t be present in the IEnumerable<Gift> that your action receives.
Add a “delete” link to the GiftEditorRow.ascx partial:
<a href="#" class="deleteRow">delete</a>
This needs to go inside the DIV with class “editorRow”, so you can handle clicks on it as follows:
$("a.deleteRow").live("click", function() { $(this).parents("div.editorRow:first").remove(); return false; });
Notice that this code uses jQuery’s “live” function, which tells it to apply the click handler not only to the elements that exist when the page is first loaded, but also to any matching elements that are dynamically added later.
You’ve now got a working editor with “add” and “delete” functionality.
Summary
I hope you can see that editing variable-length lists can be very easy. Other than the reusable Html.BeginCollectionItem() helper, I’ve just shown you every line of code needed for this particular strategy, and in total it’s just 36 lines (including the model, action methods, views, and JavaScript, but excluding lines that are purely whitespace or braces).
Download the full demo project
But what about validation?
Yes, I haven’t forgotten about that! You can already do your server-side validation any way you want – by default the model binder will respect any rules associated with your model.
In the next post, I’ll show how you can integrate this list editing strategy with ASP.NET MVC 2’s client-side validation feature.

January 28th, 2010 at 5:39 pm
And there won’t a problem when a user deletes a row and then adds a new one, creating a gad in the IDs?
January 28th, 2010 at 5:40 pm
Plus if you don’t now it, do read about
http://sparkviewengine.com/
it’s so much cleaner than WebForms
January 28th, 2010 at 6:12 pm
Aleksander, it doesn’t use sequential IDs, so it doesn’t suffer any problem like that.
Thanks for the suggestion about Spark. I’m sure you could do all this with Spark too, though for clarity I think it’s easier if I stick with the default view engine in blog posts like this.
January 28th, 2010 at 8:29 pm
Funny you should write this as I was just checking out the first version of this post. This is much cleaner than the previous solution and will save me a lot of code. Thanks!
January 29th, 2010 at 9:42 am
[…] Editing a variable length list, ASP.NET MVC 2-style - Steve Sanderson follows up on a previous post about maintaining a variable list, sharing an improved technique which has become his prefered method. […]
January 29th, 2010 at 7:42 pm
Hi Steve,
Thank you very much for the informative post!
According to Eilon Lipton, “Due to popular demand in ASP.NET MVC 2 we plan to bring back support for non-consecutive collection indexes.”
http://forums.asp.net/t/1475405.aspx
I remember seeing a confirmation as well that this made it in, but I can’t find the farking link.
If this was the case, would this simplify your solution even further?
Thanks,
January 30th, 2010 at 4:31 am
Hi Steve. Demo project you promoted won’t compile.
The error is : ‘System.Web.Mvc.ViewContext’ does not contain a definition for ‘Writer’ and no extension method ‘Writer’ accepting a first argument of type ‘System.Web.Mvc.ViewContext’ could be found (are you missing a using directive or an assembly reference?) C:\Documents and Settings\XXXXXX\Desktop\ListEditorDemo2\EditorDemo\Helpers\HtmlPrefixScopeExtensions.cs 18 30 EditorDemo
Did I miss something ?
January 30th, 2010 at 7:35 am
Hi Ali - I think you must be trying to use this project with ASP.NET MVC 1.0. Please install ASP.NET MVC 2.0 and have another go.
January 30th, 2010 at 11:31 am
Steve,
First let me say thank you. This is the best piece of information I’ve found on Binding to a list for MVC 2.
Are you running MVC 2 RC? Because that’s what I am running and I am having the same problem as ali.
January 30th, 2010 at 12:36 pm
Steve, I looked up the source for MVC 2 RC and the Writer method is indeed there. So I reinstall MVC 2 RC and that took care of the problem. Sorry for the false warning.
January 31st, 2010 at 11:13 am
Editing a variable length list, ASP.NET MVC 2-style « Steve Sanderson’s blog…
9efish.感谢你的文章 - Trackback from 9eFish…
February 3rd, 2010 at 10:58 pm
when adding a new item dynamically, why not just create the item on the client through DOM creation rather than making an ajax call to get your html.
February 6th, 2010 at 5:10 pm
Joo, I know you’d get better client-side performance by building the UI completely on the client without an ajax request. On the other hand, using the same view partial both for initial view rendering and for ajax updates is a pretty useful technique for keeping DRY and not having to use two different technologies for achieving the same thing (building that bit of your UI). It’s a tradeoff between development complexity and performance.
February 8th, 2010 at 5:51 pm
Wow, this is very clean and this addresses one of the issues that I was having in one of my projects.
Thanks a lot.
P.S. I am also waiting anxiously for your book on MVC 2.
February 8th, 2010 at 11:05 pm
Thank you for these great articles. I’ve been looking for examples of more complex view scenarios (i.e. different from 1-1 mapping between a model entity and the view), and your articles hit the spot.
Haven’t gotten through it all yet, but I am slowly learning.
I noticed that you do not use labels and that the (label) text is different from the names of the properties (i.e. Item vs. Name and Value vs. Price). I found html.labelfor and I wondered how I could have a strongly typed label that would have a different text than the name of the property. Found the solution here: http://davidhayden.com/blog/dave/archive/2009/08/13/HtmlDisplayForInAspNetMvc2.aspx
Unfortunately the value of the labels ‘for’ attr and the inputs ‘id’ attr doesn’t match after rendering. I guess this has to do with the internals of the html helper ‘BeginCollectionItem’.
<label for=gifts[f17e3c62-12ae-4885-b4a8-d7f5fc96b1cc]_Name….
<input id=gifts_f17e3c62-12ae-4885-b4a8-d7f5fc96b1cc__Name…
I will post a solution when i find it.
thanks / a
February 9th, 2010 at 8:05 am
Thanks for the post and source, but I think the js should be wrapped in:
$.ready(function() {
…
});
to wait for the jquery lib to finish loading before trying to use it to attach event handlers to the butttons/links…
February 9th, 2010 at 10:44 am
Thanks MattW
Had trouble with the jquery event handlers. Wrapping in doc ready did the trick
February 24th, 2010 at 2:29 pm
Hi Steve. Great Article!
I’ve found a conflict with names when using this approach together with a validation provider that is not tied to MVC. I’m using fluent validation to validate my entities only on the service layer and add any errors back to the MVC ModelState. However, when I validate and loop through the collections of errors to add them to the ModelStateProvider, it doesn’t add Name[GUID].MyProperty.Id. Instead, it uses Name[0].MyProperty.Id where ‘0′ is the index of a List. Is there any workaround for that?
Thanks.
March 22nd, 2010 at 3:16 pm
Excellent post - thanks a lot. To enable binding of nested lists/complex types, I have added the following to the top of the “public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)” Html extension method :
if (!string.IsNullOrEmpty(html.ViewData.TemplateInfo.HtmlFieldPrefix))
collectionName = string.Format(”{0}.{1}”, html.ViewData.TemplateInfo.HtmlFieldPrefix, collectionName);
which seems to work. Any ideas on when this might fall over, or a better way of doing it?
cheers
April 20th, 2010 at 2:47 am
Great weblog, many thanks for sharing this report
April 23rd, 2010 at 4:07 am
Hi,
I share the others appreciation of a great post!
I’m having the same problem that Fred is having with Fluent Validation, has anyone else encountered and overcome this problem?
After a cursory glance at the Fluent Validation code the only way I can see this working with Fluent Validation is to ensure that the list items are indexed in the same fashion [0], [1], etc… but this would require addition client side code??? The FluentValidationModelValidator converts the ValidationResult into a ModelValidationResult and is completely disconnected from the Guid based indexing.
May 10th, 2010 at 10:10 pm
I’m having a strange problem that I can’t get why.
I’ve copied your source 1:1 and it looks EXACTLY like yours Steve, and still I’m getting a strange error when I run it:
“c:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\root\1feb9fcc\425dfe13\App_Web_gifteditorrow.ascx.a8d08dba.gquxifmb.0.cs(166): error CS1003: Syntax error, ‘>’ expected”
Which is insane because I’ve checked and my code is exactly matching yours.
May 11th, 2010 at 11:29 pm
Thanks for the article, it was a huge time saver. Quick question, how do you manage to delete the last item? When I remove the last item and submit the form, the item is actually never removed.
Werner
May 13th, 2010 at 7:34 pm
I’m using this to support a master/detail type screen. And unless I’m using this wrong (still fairly new to MVC), there is a requirement for the default binder that the collection name match the name of the property on the master object (i.e. “Children”). So I added an overload on the BeginCollectionItem that supports a Lambada expression instead of a string:
public static IDisposable BeginCollectionItem(this HtmlHelper html, Expression<Func> expression)
{
if (!(expression.Body is MemberExpression))
throw new ArgumentException();
var member = (expression.Body as MemberExpression);
return BeginCollectionItem(html, member.Member.Name);
}
June 16th, 2010 at 5:38 am
Hi Steve. First of all let me say nice job done. Next, i m using your helper method to bind lists to collections on my master detail form e.g (Employee) and (Contacts). my controller action looks like
public ActionResult Employee(Employee emp, IEnumerable cont){}
Now the problem is that if no contact is added before submitting the form i get argument Null exception against cont that is obviously of non-nullable type. plz suggest me how i can get around this
Regards
Adeel
June 16th, 2010 at 8:02 pm
[…] Hi I am creating list of checkboxes in partial view by follwoing http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ […]
June 17th, 2010 at 3:20 pm
I found a potential problem that may affect you if you are adding new rows to a table, and the browser is Chrome or Android.
When I first created the template for the row to add I set it out as follows:
Delete
etc
Seemed logical, but this will render the hidden element between the and the element. IE goes along with this, but Chrome & Android (rightly) complain this is illegal syntax and the element will be ignored, and therefore the new row is not added.
Moving the using statement into an element fixes this, and it works fine:
Delete
Hope this is useful,
Steve
June 17th, 2010 at 3:26 pm
Ah .. sorry about the previous post. The HTML examples I included were all stripped out by the system. I’ve repeated the post below with everything encoded:
I found a potential problem that may affect you if you are adding new rows to a table, and the browser is Chrome or Android.
When I first created the template for the row to add I set it out as follows:
<tr class="editorRow">
<% using(Html.BeginCollectionItem("globalSettings")) { %>
<td>
<a href="#" class="deleteRow">Delete</a>
</td>
etc
Seemed logical, but this will render the hidden <input> element between the <tr> and the <td> element. IE goes along with this, but Chrome & Android (rightly) complain this is illegal syntax and the <input> element will be ignored, and therefore the new row is not added.
Moving the using statement into an element fixes this, and it works fine:
<tr class="editorRow">
<td>
<% using(Html.BeginCollectionItem("globalSettings")) { %>
<a href="#" class="deleteRow">Delete</a>
</td>
Hope this is useful,
Steve
June 28th, 2010 at 4:36 pm
very good demo, but I would like to replace microsoft ajax with jquery ajax, because ajax is dead project:
http://weblogs.asp.net/toddanglin/archive/2010/04/19/microsoft-ajax-client-library-is-dead-long-live-jquery.aspx
June 29th, 2010 at 9:35 pm
I am having trouble when I go a step further. I do a
So the output of the names is HighSchools[0].Grades[GUID].CourseName
Everything works as expected except for the ValidationMessageFor helpers. The correct id’s are in the modelstate collection but it is not firing the validationmessagefor.
Any thoughts?
June 29th, 2010 at 9:44 pm
Sorry it stripped the html out. Here it is repeated
I am having trouble when I go a step further. I do a
<% Using Html.BeginCollectionItem(”HighSchools[” & ViewData(”Index”) & “].Grades”)%>
So the output of the names is HighSchools[0].Grades[GUID].CourseName
Everything works as expected except for the ValidationMessageFor helpers. The correct id’s are in the modelstate collection but it is not firing the validationmessagefor.
On another page, if i just do it so that it renders HighSchools[GUID].HSName, It works fine. It only seems to fail when I go one step deeper.
Any thoughts?
July 5th, 2010 at 7:57 pm
I’m having some issues with the above example when using ViewData. I’m trying to put a selectlist into ViewData, but my view can’t seem to access it. If I take it out of the BeginCollectionItem() extension method it works just fine?
July 21st, 2010 at 6:26 am
Something very odd happens for me… I use some of this code (including HtmlPrefixScopeExtensions.cs) and everything works great on Firefox and IE but Safari for some reason would never work. I resolved by adding this after line 18 in HtmlPrefixScopeExtension:
html.ViewContext.Writer.Flush()
For whatever reason, it wasn’t doing this which meant that the .index value was just the value of the FIRST row, never more.
I’m not sure I understand it, but it’s now working. Any thoughts?
July 26th, 2010 at 7:32 am
I was really looking forward to seeing how you appended the AJAX result to the existing HTML. Turns out you hardcode it into /Scripts/listEditor.js
Apart from that, good example. Thank you.