Site Meter
 
 

Editing a variable length list, ASP.NET MVC 2-style

A 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.)

image

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.

editor-screenshot.png

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.

110 Responses to Editing a variable length list, ASP.NET MVC 2-style

  1. Aleksander

    And there won’t a problem when a user deletes a row and then adds a new one, creating a gad in the IDs?

  2. Aleksander

    Plus if you don’t now it, do read about
    http://sparkviewengine.com/
    it’s so much cleaner than WebForms

  3. Steve

    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.

  4. 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!

  5. Pingback: The Morning Brew - Chris Alcock » The Morning Brew #528

  6. 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,

  7. 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 ?

  8. Steve

    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.

  9. firefly

    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.

  10. firefly

    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.

  11. Pingback: 9eFish

  12. Joo Park

    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.

  13. Steve

    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.

  14. 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.

  15. Andreas

    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

  16. 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…

  17. Andreas

    Thanks MattW

    Had trouble with the jquery event handlers. Wrapping in doc ready did the trick

  18. Fred

    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.

  19. Trevor R

    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

  20. Great weblog, many thanks for sharing this report

  21. Joe

    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.

  22. Andy

    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.

  23. 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

  24. Rich

    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);
    }

  25. Muhammad Adeel

    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

  26. Pingback: html.checkbox – explicit value to hidden field value | The Largest Forum Archive

  27. 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

  28. 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

  29. likeuclinux

    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

  30. Dan

    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?

  31. Dan

    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?

  32. Kyle

    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?

  33. Andrew Flanagan

    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?

  34. Frank

    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.

  35. Evan

    How could I generate ascending numerical indexes instead of the GUIDs? Is the difficulty in generating the indexes or maintaining them?

  36. Johnson

    Hi Steve great post. I would like to know how I could get the non sequential index value for each list object? I need it because I need to set some Model validation errors in server side for the corresponding list object.

  37. Johnson

    HI Steve sorry for the dumb question. I extracted it from Request.Form

  38. Maik

    I just wanted to say: This is the best little helper I’ve seen so far for MVC! It saved me a lot of stupid code. Thank you VERY much! :)

  39. Jan

    Hi Steve,

    Thanks for the post.

    Can you please explain how the html structure is determined when adding an item through the #addItem click method?

  40. Bob Z

    Hi Steve,

    This is exactly what I need but when I run it the jquery doesn’t appear to append the new html to the old. The program just posts the new blank fields without the rest of the page. Rookie error?

    Bob

  41. Jon Jenkins

    I needed to get the index in the view for use on other fields and FYI to others that extending HtmlPrefixScopeExtensions.cs with this seems to work using a method of in the index “using” block.

    public static string GetCollectionItemIndex(this HtmlHelper html, string collectionName)
    {
    var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
    var itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
    return itemIndex;
    }

  42. Jon Jenkins

    Oops, my bad–this is the correct method, posted the wrong one:

    Set this in the BeginCollectionItem method once itemIndex is defined: html.ViewContext.ViewData["itemIndex"] = itemIndex;

    Then:

    public static string GetCollectionItemIndex(this HtmlHelper html, string collectionName)
    {
    return html.ViewContext.ViewData["itemIndex"].ToString();
    }

  43. Bob Z

    I originally put my references to jquery files in the aspx/ascx files (Index and GiftEditorRow). When I changed my Site.Master file from the default I got when creating the solution to the one Steve shows in his sample code (with the jquery references there), the html appending started working again. apparently my original references did not work. But now life is good.

  44. Alex

    I have the same question as Johnson. How can i read the Index value in the controller so i can manage the validation errors properly?

    It’s true that i can read the Request.Form for the index value but i have to be synchronized. If i read the 3rd item of my collection, read the 3rd item from the Index collection item.

    Is there a better way to automaticly bind the Index guid to the collection?

    alex

  45. Ben

    Hi,

    I want to send my form to a controller, but want to have the result on the same page (partial result). Is there any problem to serialize this with jQuery?
    jQuery returns nothing, if serialize gets called.

    // ItemList

    post data: $(‘form#FormID’).serialize();

    IEnumerable is null, every time… do you have an hint?

    Kind regards

  46. Ben

    Sorry, problem fixed: there was a form in a form, so the inner form is not on dom -> serialize() is empty ;-)

  47. KevinFitz

    I really like this method to this point, but I have found a scenario that doesn’t seem to be working. I am using a ViewModel and trying to bind to a select list. The select will not be set when using (using (BeginCollectionITems(“whatever”))). I am using Html.DropDownListFor(m => m.someId)

    Any ideas on how to get this to work?

  48. Pavel

    Excellent, thanks a lot. Save me a lot of time.

  49. foldip

    Hi Steve,

    I found this blog after I solved the same problem but I don’t like my solution. However I have two questions:
    1) The downloadable source even doesn’t open in VS2010 :( . Do you have any idea why?
    2) Will it work with the final MVC2? As far as I know the indexes have to be in a no-element-missing sequence order, starting with 0. At least that’s what I read everywhere and my solution does the same and when it doesn’t (bug), then it’s broken.

  50. Awesome article this dug us out of a hole – clear, concise and damn useful – thanks a million.

  51. Avery

    I think that this example will not work if you apply a common use case where the data is persisted using some ORM such as nHibernate. In such a case deleted state needs to be tracked and passed back to the contoller so the deleted rows can be removed from the persisted store.

  52. JohnWo

    I was able to incorporate your example into my view. Many thanks! My view has two models that require EditorRow function so I implemented a second EditorRowxxx.ascx file making changes that looked neccessary. The second implementation rendors the partal page correctly unless I try to add a new entry. It renders as a full page. The browser URL displays as an MVC 2 view. The content displayed is correct for the new entry. Any thought on implementing multiple model in a single view?

  53. This is such a clever example, I’ve used your other tut on validation to tie it all into a tidy MVC app using an Entity Data Model.

  54. Pingback: Adding jQuery Drop Downs to a Variable Length Data Entry Page | DeveloperQuestion.com

  55. Todd Beaulieu

    New to web development, I spent HOURS trying to figure out how to return items to the controller that weren’t sent to the original view. HOURS! This is the ONLY discussion I’ve seen so far. I don’t understand the entire thing, but I just knew this had to be possible.

    To those having problems opening the project, remove the first “project type” GUID from the .csproj file. It’s an old MVC reference.

  56. Kevin Warner

    Has anybody gotten this to work with a
    DropDownListFor(m => m.item, Model.MySelectList)?

  57. Mike

    I’m having the same issue that Bob Z. was having a few posts before. I’ve put all the Script references in my Site.Master, but it is failing to append any new fields to the existing page and just give me the new fields and the rest of the page is blank.

  58. Mike

    I resolved my issue by wrapping the jquery stuff in doc ready, as someone had suggested earlier. And I agree with many previous posters that this is a great resource. Thank you for writing the article.

    Has anyone had any experience with trying to save this data back to a database using Entity Framework? I’ve tried everything I can think of with my UpdateModel() and SaveChanges() methods and nothing is working at all for saving the data.

    Any help at all would be greatly appreciated!

  59. Dani

    Should this code work if IEnumerable was part of a larger viewmodel? I am trying to use this technique in a large form with 2 lists but when I post back, the Viewmodel.Order that should’ve contained 2 lists of items and activities return with the lists empty. (not even with the original values, which I guess disappeared due to the id naming trick.

  60. Pingback: ASP.NET MVC2 ORDER/ORDER LINE (Master/Detail) View – how to tie everything up - Question Lounge

  61. dmorgan

    Hi, excellent post thanks!
    I got a problem with the GetIdsToReuse in case of validation errors. If the Post action returns a RedirectToAction (for the PRG pattern) then the HttpContext.Request doesn’t include the collection indexes.
    Any ideas of how to solve that?

  62. Dan

    Very helpful but how do you do multiple levels? My object has a collection of States and each state has a collection of Counties. I can get the States back but the counties won’t load into the model.

  63. Thank you a bunch for sharing this with all of us you actually realize what you are speaking about! Bookmarked. Please also visit my web site =). We may have a link exchange arrangement between us!

  64. Pingback: Model Validation in ASP.NET MVC3 | Esencia Development

  65. ncsuwolf

    I have converted the project to MVC3 using the Razor engine. The problem I’m having is that when I add a new row, I am losing the surrounding div “editor Rows”, or basically, the page the partial is nested within. Here is the Index Page:

    @model IEnumerable

    @{
    ViewBag.Title = “Index”;
    Layout = “~/Views/Shared/_Layout.cshtml”;
    }

    Procedure Code Entry
    @{
    Html.BeginForm();

    @foreach (var item in Model)
    {
    Html.RenderPartial(“ProcedureEditorRow”, item);
    }

    @Html.ActionLink(“Add Another Procedure…”, “BlankEditorRow”, new { ViewContext.FormContext.FormId }, new { id = “addItem” })

    }

    Here is the Partial:
    @model ClaimSubmission.Models.ProcedureLineEntity
    @using ClaimSubmission.Helpers;

    @using (Html.BeginCollectionItem(“procedures”))
    {
    Date Of Service
    Html.TextBoxFor(x => x.DateOfService);

    Procedure Code
    Html.TextBoxFor(x => x.ProcedureCode);

    Description
    Html.TextBoxFor(x => x.ProcedureCodeDescription);

    Tooth/Surface
    Html.TextBoxFor(x => x.ToothSurface);

    Fee $
    Html.TextBoxFor(x => x.Fee);

    Remove
    }

    The Entity that I’m using:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.ComponentModel.DataAnnotations;

    namespace ClaimSubmission.Models
    {
    public class ProcedureLineEntity
    {
    [Required]
    public DateTime DateOfService { get; set; }
    [Required]
    public string ProcedureCode { get; set; }
    public string ProcedureCodeDescription { get; set; }
    public string ToothSurface { get; set; }
    [Range(0.01, double.MaxValue, ErrorMessage = "Please enter a fee above zero.")]
    public decimal? Fee { get; set; }
    }
    }

    and the javascript:
    $.ready(function () {
    $(“#addItem”).click(function () {
    $.ajax({
    url: this.href,
    cache: false,
    success: function (html) { $(“#editorRows”).append(html); }
    });
    return false;
    });

    $(“a.deleteRow”).live(“click”, function () {
    $(this).parents(“div.editorRow:first”).remove();
    return false;
    });
    });

    I include the javascript in the _layout.cshtml file as follows:

    @ViewBag.Title

    My MVC Application

    @Html.Partial(“_LogOnPartial”)

    @Html.ActionLink(“Home”, “Index”, “Home”)
    @Html.ActionLink(“About”, “About”, “Home”)

    @RenderBody()

    It’s very strange, after the first page load the div for editRows is there, but after adding a row, the div is gone and all you see is the new row. No “Add Another” nor the “Finished” button (or even the title). What am I missing, that is blowing away the page the partial resides in?

    Thanks,

    ncsuwolf

  66. ncsuwolf

    apparently my posting had a lot of items stripped out when I posted. Sorry, still trying to work that out.

    ncsuwolf

  67. qerky

    Hi!
    Very good article.
    But I’m curious how it works?
    I mean the generating ID. How does the model know which inputs with which ids should be in the fields of model?
    Can you explain it or give me link to site where it is explained?
    Can I generate values of ID with my convention? Or can I use pure javascript without ajax to create a new items of gifts?
    I saw that if I delete the hidden input then gits will not maped to model and I’m wonder why…

  68. qerky

    I found answer for this question. You can delete my comments. Sry for a problem. And once again… great article.

  69. @ncsuwolf – I haven’t figured out why it’s happening, but I did find a workaround for the issue you are having with the content. If you do the following instead of the .append(html), it should work:

    $(‘#editorRows’).html($(‘#editorRows’).html() + html);

    I don’t like the solution and I am still planning on figuring out why .append is stripping the HTML off of what is provided, but for now, it should get you past this point. If I figure out the issue I’ll post here again.

  70. ncsuwolf

    Does anyone know how to find the index of each row and pass it to a jQuery function. I have 2 lookups on each row and I need to be able to determine the row of elements that I am going to update if the lookups are successful.

    Thanks.

  71. ncsuwolf

    Jon,
    I just saw where you were accessing the index using
    html.ViewContext.ViewData["itemIndex"] = itemIndex;

    How were you using this? Did you pass the index into a jQuery method? Referencing it in your controller? I am trying to pass the index to a jQuery method so that I can populate fields on the row with a lookup, looking for anything similar that someone may have done.

    Thanks,

    Scott

  72. ncsuwolf

    Is anyone else having any issues with Client Side Validation? I am using this solution with unobtrusive jjQuery and the following weird error is happening: The first row works (most of the time) but each new row does not. I tried to issue a $validator.unobtrusive.parse(‘div I’m appending to’) after the add is successful. Does anyone else have this issue? Is there a way to make the validators work for each new row?

    Thanks,

    Scott

  73. Kumar

    Will this work in ASP.NET MVC 3 and Razor? I have even tried hardcoding the indexes for each textboxes. The controller IList is always null

    Works well in ASP.Net MVC 2.

  74. Helton Valentini

    Has anybody gotten this to work with a
    DropDownListFor(m => m.item, Model.MySelectList)? It doesn’t select the option relative to the model option. In any other scenario it works perfectly.

  75. davvidd

    $(document).ready(function () {
    $(“#Grupa”).change(function () {
    var url = ” + “Comenzi/ForProduse”;
    var ddlsource = “#Grupa”;
    var ddltarget = “#Produs”;
    $.getJSON(url, { id: $(ddlsource).val() }, function (data) {
    $(ddltarget).empty();
    $.each(data, function (index, optionData) {
    $(ddltarget).append(“” + optionData.Text + “”);
    });

    });
    });
    });

    Grupa: x.Grupa, Model.ListaGrupe) %>
    Produsul: x.Produs, Model.ListaProduse) %>
    Cantitate: x.Cantitate) %>
    Pret: x.Pret, new { size = 4})%>
    TVA: x.TVA) %>
    Total: x.Total) %>

    Sterge

    How can I get this to call the JsonResult method cause it’s not working? I want to dynamically change the list in the second dropdownlist when I change the selected value of the first dropdownlist.

  76. davvidd

    $(document).ready(function () {
    $(“#Grupa”).change(function () {
    var url = ” + “Comenzi/ForProduse”;
    var ddlsource = “#Grupa”;
    var ddltarget = “#Produs”;
    $.getJSON(url, { id: $(ddlsource).val() }, function (data) {
    $(ddltarget).empty();
    $.each(data, function (index, optionData) {
    $(ddltarget).append(“” + optionData.Text + “”);
    });

    });
    });
    });

    Grupa: x.Grupa, Model.ListaGrupe) %>
    Produsul: x.Produs, Model.ListaProduse) %>
    Cantitate: x.Cantitate) %>
    Pret: x.Pret, new { size = 4})%>
    TVA: x.TVA) %>
    Total: x.Total) %>

    Sterge

  77. davvidd

    Can’t seem to post the whole code. Anyway, this is the partial view (“editorRow”) and i have Grupa and Produse (and ListaGrupa and ListaProduse for dropdownlist).

    div class=”editorRow”

    script type=”text/javascript”
    $(document).ready(function () {
    $(“#Grupa”).change(function () {
    var url = ” + “Comenzi/ForProduse”;
    var ddlsource = “#Grupa”;
    var ddltarget = “#Produs”;
    $.getJSON(url, { id: $(ddlsource).val() }, function (data) {
    $(ddltarget).empty();
    $.each(data, function (index, optionData) {
    $(ddltarget).append(“” + optionData.Text + “”);
    });

    });
    });
    });
    — endscript

    using(Html.BeginCollectionItem(“comds”))
    Html.DropDownListFor(x => x.Grupa, Model.ListaGrupe)
    Html.DropDownListFor(x => x.Produs, Model.ListaProduse)

  78. Hi,

    Thanks for the explanation, it’s a very useful post.

    I downloaded the sources, compiled them in VS 2010 and the app is working fine,but when I tried creating a project from scratch in VS 2010 + MVC3, the IEnumerable on the controller is always null !!

    Could you please tell me if there is an important step that’s not covered in this post ?

    Thanks in advance,
    Kiran Banda

  79. I think I figured out after submitted my question :)

    For the benefit of all : Please ensure that the string (gifts) here : using(Html.BeginCollectionItem(“gifts”)) matches the name of the parameter in your controller:

    public ActionResult Index(IEnumerable gifts)

    Cheers,
    Kiran Banda

  80. OneSman7

    Dear Steve!

    Many thanks for your awesome book on ASP MVC 2! It helped me getting started with development for this platform a lot!

    I had to modify it a bit though to work in MVC 3:

    html.ViewContext.Writer.WriteLine(string.Format(“”, collectionName, html.Encode(itemIndex)));

    return BeginHtmlFieldPrefixScope(html, string.Format(“[{0}]“, itemIndex));

    The Changes are: 1) usage of id=\”Index\” in hidden input 2) only index usage in prefixes (string.Format(“[{0}]“, itemIndex)).

    For those struggling to get indexes after html generation (in action method, or, as I, in custom model binder) try getting them the same way extension method does. A few tips when using custom binders:

    1) For each binder make a special key and make it public. For example add this to binder class:

    public static string CollectionPrefix
    { get { return “_key”; } }
    2) When using extension, pass this key to it:

    @using (Html.BeginCollectionItem(WebUI.Infrastructure.AwesomeBinder.CollectionPrefix))

    3) Get indexes when binding (I used only Form because of my logic, you may use Request):

    NameValueCollection postedData = controllerContext.HttpContext.Request.Form;
    string[] indexKeysCollection = postedData[CollectionPrefix+"_index"].Split(‘,’);

  81. OneSman7

    “I had to modify it a bit though to work in MVC 3″
    I am referring to the extension method in your demo project of course, not your book :D .

  82. Vince Bray

    Thanks for this helpful post! One challenge I’m having is that my model is used in several views, and validation needs to skip some fields in some of the views. I’m not sure how to address them using ModelState.Remove()

    The data structure is a parent/child where the children are actually the objects tagged with the Guid here. So my syntax would need to be

    ModelState.Remove(“Child[Guid].StartTime”);

    Is there any help for this? Thanks!

  83. Vince Bray

    Stumbled on it, actually quite simple. Loop on the keys in ModelState and remove when they match the field names you want.

    foreach (var k in ModelState.Keys)
    {
    if (k.Contains(“Field1″) ||
    k.Contains(“Field2″) ||
    k.Contains(“Field3″)
    {
    ModelState.Remove(k);
    }
    }

  84. Tony

    Looks like we have to use jquery 1.3.2 for this to work. Are there any modifications to the jscript code that would be compatible with jquery 1.5.1 or 1.6.1? If I try using 1.5.1 instead of 1.3.2, I get a jscript object expected error.

  85. Tony

    Also, is there any way to apply a jquery mask on the input fields?

    $(“#InputName”).mask(“99/99/9999″);

    How can use the generated input id’s to apply this style of jquery mask?

  86. Richard

    Thanks for this – this is great!

    What if your partial view had multiple rows of display for each record of data? I’m having trouble with the display when the rows are appended.

  87. I saw your comment on how to get this to work when you are adding table rows and not divs but I cannot get it to work using MVC 3 and Razor. Where do you put the closing ‘}’? I get parsing errors no matter where I place it.

    Thanks

  88. This is the best tutorial showing how to edit List items or the likes!

    It’s simple, straightforware, and it works. Not like most other tutorials which are about this topic.

    Sadly enough it took me almost 2 hours to find this page through google and stackoverflow.

    Thanks again!

  89. HABo

    Hi Steve, I am trying to achieve some thing very similar to what you have explained here. I am working on a MVC 3.0 Project I am Rending an Action in to a main View, and that action returns a Partial view with a form, and I have a link in the partial View form to add another form, as lon as user keep clicking that link the form should keep repeating. I am New to MVC and I am not able figure it out how to get this done, Any help would be greatly appreciated. Please look at my code.
    In Main VIew i have this @Html.Action(“ActionName”)
    In ControllerI have this action
    public ActionResult Add()
    { return View(“_FormVIew”); }
    and my _FormVIew looks like this
    @using (Html.BeginForm(“”))
    {
    @Html.ValidationSummary(true)

    @Html.LabelFor(model => model.Units)

    @Html.EditorFor(model => model.Units)
    @Html.ValidationMessageFor(model => model.Units)

    @Html.LabelFor(model => model.Quantity)

    @Html.EditorFor(model => model.Quantity)

    |
    @Html.ActionLink(“Add Another”, “Add”, null, new { id = 1 })

    Thank you very much for your time

  90. HaBo

    When I try to do the same demonstrated code in MVC 3.0 Razor Engine, I get Compilation Error
    Extension method must be defined in a non-generic static class
    Helpers\HtmlPrefixScopeExtensions.cs

    any help to fix this?

  91. SW

    Hello Steve, Thanks for the post, it helped a lot. But I am having a problem with remote validation, I think its because of the parameter name in the remote validation method since it uses guids. It’s possible to apply remote validation to a variable lenght list? Can you give a example about it?

  92. vasek

    For anyone still interested, it’s easy to get a strongly typed BeginCollectionItemFor().

    The extension definition is:
    public static IDisposable BeginCollectionItemFor(this HtmlHelper html, Expression<Func> expression)
    {
    var htmlFieldName = ExpressionHelper.GetExpressionText(expression);
    var collectionName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName);

    return BeginCollectionItem(html, collectionName);
    }

    for a model:
    Bar
    {
    Foo[] foos;
    }

    you would have in a view:
    @foreach (var foo in this.Model.foos)
    {
    using (Html.BeginCollectionItemFor(model => model.foos))
    {
    @Html.Partial(“Foo”, foo)
    }
    }

    html will then render input with name=”foos[guid].SomePropertyOfFoo”

  93. Kenan

    How can i make this work with nested lists? I.E a ViewModel that has a list of Gifts.

  94. Yogesh

    Hi..Thanks for the great post.
    But I have a issue when I add a new record.
    I am using a ViewModel and trying to bind to a select list with View Data (Html.DropDownListFor).
    It throws error that the ViewData SelectList doesnt have the key (which contain the GUID).
    All works well with text fields.
    Please help !!

  95. Mahsa

    Thanks sooooooooooooo much for this useful post. I’ve been looking for different solutions,but yours was the best and worked just the way I wanted. Thanks

  96. excellent post ! works perfectly with Razor and MVC3.

    the only glitch was decimal, but people i have a different culture than en-US i had to write me one ModelBinder !

    Thanks again !

  97. Brad

    This has worked great for me. Thank you. Some things that I’ll note that I don’t think were mentioned in the article. The Name you set in the BeginCollectionItem block must match the member name of the collection on the class (BeginCollectionItem(“Foos”) where MyFoo.Foos is a List) My other issue was my fault where my BeginForm was split across a div tag. With that adding additional rows would never save or even get posted back to the server. It was quite frustrating.

  98. Gregg

    @Kiran Banda: Thank you so much for that comment. I was having the worst time trying to figure out why my IEnumerable model collection was always null after submitting the form.

    @Steven S.: Thank you for all of the excellent posts…and KnockoutJS…and your books. ;)

  99. Mehdi

    I also want to say Thank You, It was indeed useful for me :)

  100. Naseer Ahmad

    I see many comments about this NOT working in Razor. For those who want to run in MVC3. the controller needs to be updated to return the PartialViewResult and NOT ActionResult. Here is the updated code:

    public PartialViewResult Add()
    {
    return PartialView(“_NewGiftRow”, new Gift());
    }

  101. Lawrence

    Hi guys, im struggling to to create a view from 2 tables. can anyone help me with that.

  102. parajao

    For those who want inner complex fields to be correctly bound add this method to the HtmlPrefixScopeExtensions:

    public static IDisposable BeginCollectionSubItem(this HtmlHelper html, string subItemName)
    {
    if (!string.IsNullOrEmpty(html.ViewData.TemplateInfo.HtmlFieldPrefix))
    subItemName = string.Format(“{0}.{1}”, html.ViewData.TemplateInfo.HtmlFieldPrefix, subItemName);

    return BeginHtmlFieldPrefixScope(html, subItemName);
    }

    So if you have a class House with a collection of Rooms and inner field Floor
    class House { public ICollection Rooms }
    class Room { public Floor Floor {get;set;} }
    class Floor { public double Length{get;set;} public double Width{get;set;}
    and want it to be correctly bound
    In your _RoomRow.cshtml you should have:
    @using (Html.BeginColletionItem(“Rooms”)
    { using (Hmtml.BeginCollectionSubItem(“Floor”)
    { Html.RenderPartial(“_Floor”); }
    }
    Hope this helps

    pj

  103. Niall

    Thanks alot for posting this. It is exactly what I am looking for, a very elegant and simple solution! Fantastic

  104. Manoj Kumar Banga

    Great weblog, many thanks for sharing this report

  105. obin

    I’ve read several good stuff here. Certainly worth bookmarking for revisiting. I wonder how much effort you put to create such a great site.

    dog boarding calgary

  106. Micky

    Just thought that I’d give thanks for the tutorial. I’ve managed to incorporate this into an MVC3 application and it worked perfectly.

  107. Satheeshkumar.RC

    Hi,
    That’s really a great stuff.
    Currently I am using this in my project and there is a small problem in model binding.When there is a hidden field for any property of a collection object ,then it’s value is not binded.

    Could you please help me out of this.

  108. Ok. So I have it all hooked up but it’s not working. Some differences in my app are that I have an Area where this code is hooked up. My app is MVC3 w/ Razor views. And I am using jquery-1.5.1. You are hooking this up in the root and using Jquery 1.3.2. When I click on the “Add Item” it takes me to the actual URL of the Add Method which returns the partial view. here is my url as the example -http://localhost:53023/Client/MyMoveItems/Add Any thoughts?

  109. VERY CRUCIAL. If you are converting to MVC3 / Razor (as I did), one thing you need to remember to do is put the listEditor.js script tag just before the closing tag. That one thing gave me problems for hours. And just as the solution to a problem is always the last place you look, I finally found this when comparing to Steven’s source and fixed it. I mistakenly put the listEditor.js in the tag of my Layout.cshtml when I was initially converting. Bah! Hope I save someone else this headache. ( :