Editing Variable Length Reorderable Collections in ASP.NET MVC – Part 1: ASP.NET MVC Views

Introduction

I have decided to write a short series of blog posts on editing collections and more specifically variable length collections in ASP.NET MVC. I will take two different implementation approaches:

  1. In Part 1 I look at implementing collection editing by sticking to facilities provided to us by ASP.NET MVC such as views, partial views, editor templates, model binding, model validation, etc.
  2. In Part 2 I further enhance the sample by using jQuery Templates, but still utilize the same ASP.NET MVC views from Part 1.
  3. In Part 3 I will look at Knockout JS templating, data binding and moving variable length reorderable collection editing completely on the client side with the MVVM pattern and JSON data submission and model binding.

The aspects I will consider are:

  • Dynamically adding, removing and reordering items to/from the collection
  • Validation implications
  • Code Reusability and Refactoring implications

I will assume that you are already familiar with ASP.NET MVC and basic JavaScript concepts.

Source Code

All source code is available on GitHub

The Sample

What I am going to build is a little sample where we have a user who has a list of favourite movies. It will look roughly like on the image below and will allow for adding new favourite movies, removing favourite movies and also reordering them up and down using the drag handler.

image

Domain Model

The domain model is basically:

public class User
{
    public int? Id { get; set; }
    [Required]
    public string Name { get; set; }
    public IList<Movie> FavouriteMovies { get; set; }
}

and

public class Movie
{
    [Required]
    public string Title { get; set; }
    public int Rating { get; set; }
}

Let’s get cracking!

An Edit View

Let’s start by creating a first-pass edit view for our Person to look like the one on the image above:

@model CollectionEditing.Models.User
@{ ViewBag.Title = "Edit My Account"; }

<h2>Edit</h2>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>My Details</legend>

        @Html.HiddenFor(model => model.Id)

        <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>
    </fieldset>
    
    <fieldset>
        <legend>My Favourite Movies</legend>

        @if (Model.FavouriteMovies == null || Model.FavouriteMovies.Count == 0) {
            <p>None.</p>
        } else {
            <ul id="movieEditor" style="list-style-type: none">
                @for (int i=0; i < Model.FavouriteMovies.Count; i++) {
                    <li style="padding-bottom:15px">
                        <img src="@Url.Content("~/Content/images/draggable-icon.png")" style="cursor: move" alt=""/>

                        @Html.LabelFor(model => model.FavouriteMovies[i].Title)
                        @Html.EditorFor(model => model.FavouriteMovies[i].Title)
                        @Html.ValidationMessageFor(model => model.FavouriteMovies[i].Title)

                        @Html.LabelFor(model => model.FavouriteMovies[i].Rating)
                        @Html.EditorFor(model => model.FavouriteMovies[i].Rating)
                        @Html.ValidationMessageFor(model => model.FavouriteMovies[i].Rating)

                        <a href="#" onclick="$(this).parent().remove();">Delete</a>
                    </li>
                }
            </ul>
            <a href="#">Add another</a>
        }

        <script type="text/javascript">
            $(function () {
                $("#movieEditor").sortable();
            });
        </script>
    </fieldset>
    
    <p>
        <input type="submit" value="Save" />
        <a href="/">Cancel</a>
    </p>
}

The view is creating a list of editing controls for each of the movies in Person.FavouriteMovies. I am using a jQuery selector and dom function to remove a movie when the user clicks “Delete”  and also a jQuery UI Sortable to make the items from the HTML list drag and droppable up and down.

With this done we immediately face the first problem: We haven’t implemented the “Add another”. Before we do that let’s consider how ASP.NET MVC model binding of collections works.

ASP.NET MVC Collection Model Binding Patterns

There are two patterns for model binding collections in ASP.NET MVC. The first one you have just seen:

@for (int i=0; i < Model.FavouriteMovies.Count; i++) {
    @Html.LabelFor(model => model.FavouriteMovies[i].Title)
    @Html.EditorFor(model => model.FavouriteMovies[i].Title)
    @Html.ValidationMessageFor(model => model.FavouriteMovies[i].Title)
…
}

which generates similar HTML:

<label for="FavouriteMovies_0__Title">Title</label>
<input id="FavouriteMovies_0__Title" name="FavouriteMovies[0].Title" type="text" value="" />
<span class="field-validation-error">The Title field is required.</span>

This is really great for displaying collections and editing static length collections, but problematic when we want to edit variable length collections, because:

  1. Indices have to be sequential (0, 1, 2, 3, …). If they aren’t ASP.NET MVC stops at the first gap. E.g. if you have item 0, 1, 3, 4 after the model binding has finished you will end up with a collection of two items only – 1 and 2 instead of four items.
  2. If you were to reorder the list in the HTML ASP.NET MVC will apply the indices order not the fields order when doing model binding.

This basically means that add/remove/reorder scenarios are no go with this. It’s not impossible but it will be big big mess tracking add/remove/reorder actions and re-indexing all field attributes.

Now, someone might say – “Hey, why don’t you just implement a non-sequential collection model binder?” .

Yes, you can write the code for a non-sequential collection model binder. You will however face two major issues with that however. The first being that the IValueProvider doesn’t expose a way to iterate through all values in the BindingContext which you can workaround by hardcoding the model binder to access the current HttpRequest Form values collection (meaning that if someone decides to submit the form via Json or query parameters your model binder won’t work) or I’ve seen one more insane workaround which checks the BindingContext one by one from CollectionName[0] to CollectionName[Int32.MaxValue] (that’s 2 billion iterations!).

Second major issue is that once you create a sequential collection from the non-sequential indices and items and you have a validation error and you re-render the form view your ModelState will no longer match the data. An item that used to be at index X is now at index X-1 after another item before it was deleted, however the ModelState validation message and state still point to X, because this is what you submitted.

So, even a custom model binder won’t help.

Thankfully there is a second pattern, which mostly helps for what we want to achieve (even though I don’t think it was designed to solve exactly this):

<input type="hidden" name="FavouriteMovies.Index" value="indexA"/>
<input name="FavouriteMovies[indexA].Title" type="text" value="" />
<input name="FavouriteMovies[indexA].Rating" type="text" value="" />
<input type="hidden" name="FavouriteMovies.Index" value="indexB"/>
<input name="FavouriteMovies[indexB].Title" type="text" value="" />
<input name="FavouriteMovies[indexB].Rating" type="text" value="" />

Notice how we have introduced an “.Index” hidden field for each collection item. By doing that we tell ASP.NET MVC’s model binding “Hey, don’t look for a standard numeric collection index, but instead look for the custom Index value we have specified and just get me the list of items in a collection when you are done”. How does this help?

  1. We can specify any index value we want
  2. The index doesn’t have to be sequential and items will be put in the collection in the order they are in the HTML when submitted.

Bam! That’s solves most, but not all of our problems.

The Solution

Firstly, ASP.NET MVC doesn’t have HTML helpers to generate the “[something].Index” pattern which is major problem since it means we can’t use validation and custom editors. We can fix that by utilizing some ASP.NET templating fu. What we are going to do is move the Movie editor to a its own partial view (MovieEntryEditor.cshtml):

@model CollectionEditing.Models.Movie
           
<li style="padding-bottom:15px">
    @using (Html.BeginCollectionItem("FavouriteMovies")) {
        <img src="@Url.Content("~/Content/images/draggable-icon.png")" style="cursor: move" alt=""/>

        @Html.LabelFor(model => model.Title)
        @Html.EditorFor(model => model.Title)
        @Html.ValidationMessageFor(model => model.Title)

        @Html.LabelFor(model => model.Rating)
        @Html.EditorFor(model => model.Rating)
        @Html.ValidationMessageFor(model => model.Rating)

        <a href="#" onclick="$(this).parent().remove();">Delete</a>
    }
</li>

And update our Edit view to use it:

<ul id="movieEditor" style="list-style-type: none">
    @foreach (Movie movie in Model.FavouriteMovies) {
        Html.RenderPartial("MovieEntryEditor", movie);
    }
</ul>
<p><a id="addAnother" href="#">Add another</a>

Notice two things – firstly the Movie partial edit view uses standard Html helpers and secondly there is a call to something custom called Html.BeginCollectionItem. You might even ask yourself: Wait a second. This won’t work, because the partial view will produce names such as “Title” instead of “FavouriteMovies[xxx].Title”, so let me show you the source code of Html.BeginCollectionItem:

public static IDisposable BeginCollectionItem<TModel>(this HtmlHelper<TModel> html,                                                       string collectionName)
{
    string itemIndex = Guid.NewGuid().ToString();
    string collectionItemName = String.Format("{0}[{1}]", collectionName, itemIndex);

    TagBuilder indexField = new TagBuilder("input");
    indexField.MergeAttributes(new Dictionary<string, string>() {
        { "name", String.Format("{0}.Index", collectionName) },
        { "value", itemIndex },
        { "type", "hidden" },
        { "autocomplete", "off" }
    });

    html.ViewContext.Writer.WriteLine(indexField.ToString(TagRenderMode.SelfClosing));
    return new CollectionItemNamePrefixScope(html.ViewData.TemplateInfo, collectionItemName);
}

private class CollectionItemNamePrefixScope : IDisposable
{
    private readonly TemplateInfo _templateInfo;
    private readonly string _previousPrefix;

    public CollectionItemNamePrefixScope(TemplateInfo templateInfo, string collectionItemName)
    {
        this._templateInfo = templateInfo;

        _previousPrefix = templateInfo.HtmlFieldPrefix;
        templateInfo.HtmlFieldPrefix = collectionItemName;
    }

    public void Dispose()
    {
        _templateInfo.HtmlFieldPrefix = _previousPrefix;
    }
}

This helper does two things:

  • Appends a hidden Index field to the output with a random GUID value (remember that using the .Index pattern an index can be any string)
  • Scopes the execution of the helper via an IDisposable and sets the template rendering context (html helperes and display/editor templates) to be “FavouriteMovies[GUID].”, so we end up with HTML like this:

<input autocomplete="off" name="FavouriteMovies.Index" type="hidden" value="6d85a95b-1dee-4175-bfae-73fad6a3763b" />
<label>Title</label>
<input class="text-box single-line" name="FavouriteMovies[6d85a95b-1dee-4175-bfae-73fad6a3763b].Title" type="text" value="Movie 1" />
<span class="field-validation-valid"></span>

This solves the problem of using Html field templates and basically reusing ASP.NET facilities instead of having to write html by hand, but it leads me to the second quirk that we need to address.

Let me show you the second and final problem. Disable client side validation and delete the title of e.g. “Movie 2” and click submit. Validation will fail, because Title of a movie is a required field, but while we are shown the edit form again there are no validation messages:

image

Why is that? It’s the same problem I mentioned earlier in this post. Each time we render the view we assign different names to the fields, which do not match the ones submitted and leads to a ModelState inconsistency. We have to figure out how to persist the name and more specifically the Index across requests. We have two options:

  1. Add a hidden CollectionIndex field and CollectionIndex property on the Movie object to persist the FavouriteMovies.Index. This however is intrusive and suboptimal.
  2. Instead of polluting the Movie object with an extra property be smart and in our helper Html.BeginCollectionItem reapply/reuse the submitted FavouriteMovies.Index form values.

Let’s replace in Html.BeginCollectionItem this line:

string itemIndex = Guid.New().ToString();

with:

string itemIndex = GetCollectionItemIndex(collectionIndexFieldName);

And here’ is the code for GetCollectionItemIndex:

private static string GetCollectionItemIndex(string collectionIndexFieldName)
{
    Queue<string> previousIndices = (Queue<string>) HttpContext.Current.Items[collectionIndexFieldName];
    if (previousIndices == null) {
        HttpContext.Current.Items[collectionIndexFieldName] = previousIndices = new Queue<string>();

        string previousIndicesValues = HttpContext.Current.Request[collectionIndexFieldName];
        if (!String.IsNullOrWhiteSpace(previousIndicesValues)) {
            foreach (string index in previousIndicesValues.Split(','))
                previousIndices.Enqueue(index);
        }
    }

    return previousIndices.Count > 0 ? previousIndices.Dequeue() : Guid.NewGuid().ToString();
}

We get all submitted values for e.g. “FavouriteMovie.Index” put them in a queue, which we store for the duration of the request. Each time we render a collection item we dequeue its old index value and if none is available we generate a new one. That way we preserve the Index across requests and can have a consistent ModelState and see validation errors and messages:

image

All that is left is to implement the “Add another” button functionality and we can do that easily by appending a new row to the movie editor, which we can fetch using Ajax and use our existing MovieEntryEditor.cshtml partial view like that:

public ActionResult MovieEntryRow()
{
    return PartialView("MovieEntryEditor");
}

And then add the follwing “Add Another” click handler:

$("#addAnother").click(function () {
    $.get('/User/MovieEntryRow', function (template) {
        $("#movieEditor").append(template);
    });
});

Done.

Conclusion

While not immediately obvious editing variable length reorderable collections with standard ASP.NET MVC is possible and what I like about this approach is that:

  • We can keep using traditional ASP.NET html helpers, editor and display templates (Html.EditorFor, etc.) with in our collection editing
  • We can make use of the ASP.NET MVC model validation client and server side

What I don’t like that much however is:

  • That we have to use an AJAX request to append a new row to the editor.
  • That we need to use the name of the collection in the movie editor partial view, but otherwise when doing the standalone AJAX get request the name context won’t be properly set for the partial template fields.

I would love to hear your thoughts. The sample source code is available on my GitHub

Next: Part 2 and how to avoid having an AJAX request with the use of jQuery Templates

Be Sociable, Share!
  • Miguel Miranda

    Hi,
    I have tested  your code and works perfectly.
    I have tried to pass a table row, instead of LI, but I can’t. My view don’t render the table, although it’s passed by ajax.

    I don’t understand. Can you help me?

    • http://ivanz.com/ Ivan Zlatev

      You will have to change the way the partial is appended if you are using a table. The semantics are not the same as in list, because a table has a more complex structure. Maybe this will help you – http://stackoverflow.com/questions/171027/add-table-row-in-jquery

      • Miguel Miranda

        Thanks 

  • joe

    Great article. Wrapping up by reusing MovieEntryEditor = win.

  • Muhammad Adeel Zahid

    Hello Ivan, 
    i have blogged about master detail form in asp.net mvc using similar technique and have incorporate jquery template to avoid sending ana ajax request for rendering new row. you can find this article at http://zahidadeel.blogspot.com/2011/05/master-detail-form-in-aspnet-mvc-3-ii.html

    • http://ivanz.com/ Ivan Zlatev

      Hi,

      Thanks for sharing. I actually just posted Part 2 that covers the same thing (apart from the client-side validation) and it appears I’ve taken a slightly different approach You can check here – http://ivanz.com/2011/06/20/editing-variable-length-reorderable-collections-in-asp-net-mvc-part-2/

  • Miguel Miranda

    Hi, as I said good work.
    I have added some data annotations to Movie, and they work if I render the partial view on the View, but don’t work if I use

    public ActionResult MovieEntryRow(){    
    return PartialView(“MovieEntryEditor”);
    }

    Do you have any idea, why?

  • A Seif83

    thanks,very good work
    i have problem with this solution and entity framework , in edit action child list DB table foreign key is null and duplicate the child lists rows with correct  foreign key .i know it is child list entity state problem ,Do you know how to fix this.

    thanks in advance

    • http://profiles.google.com/a.wrochna Anna Kosieradzka

      I too am using the entity framework (code-first) and am handling this this way:

      public class User
      {
          (…)
          // That’s in the database:
          public virtual ICollection FavouriteMovies { get; set; }
          // That’s what submitted in the form when you save:
          public IList FavouriteMoviesList { get; set; }
         
      }

      Now, the database won’t remember the order of the movies, so I’ve added a “Position” property to them.

      When I construct my Edit form, I take the movies from the database:

      foreach (var movie in Model.FavouriteMovies.OrderBy(x => x.Position))…

      When it’s submitted, I copy the contents of FavouriteMoviesList to the database.

      Hope this helps.

    • http://profiles.google.com/a.wrochna Anna Kosieradzka

      I too am using the entity framework (code-first) and am handling this this way:

      public class User
      {
          (…)
          // That’s in the database:
          public virtual ICollection FavouriteMovies { get; set; }
          // That’s what submitted in the form when yu save
          public IList FavouriteMoviesList { get; set; }
          
      }

      Now, the database won’t remember the order of the movies, so I’ve added a “Position” property to them.

      When I construct my Edit form, I take the movies from the database:

      foreach (var movie in Model.FavouriteMovies.OrderBy(x => x.Position))

      When it’s submitted, I copy the contents of FavouriteMoviesList to the database.

      Hope this helps.

  • Miguel Angel Goicochea

    Hello thanks for your example first of all. I’m having a problem with validations when using dropdowns, here is my code:

    @Html.DropDownListFor(x => x.FormId, ((List)ViewData["FormIdList"]) .ToSelectList(“FormName”,”FormName”,Model.FormId ?? “”), new { @class = “Text”, onchange = “updateFieldDropDown(this);” })

    @Html.ValidationMessageFor(x => Model.FormId)

    FormId is marked as required, but the validation is not fired, I tried the same using a TextBox and it does work, what could be the problem here?

    The generated HTML looks like this

        Default
       
        WorkflowSearch

    • http://ivanz.com/ Ivan Zlatev

      What do you mean when you say “validation is not fired”:

      1) The client side validation doesn’t kick in before you submit the form. If that’s the case then there is not much you can do, because the client-side validation won’t fire for newly added items (because it doesn’t handle dom changes).

      or 

      2) After you submit the form inside your controller POST action ModelState.IsValid returns true even though FormId is marked as [Required] and there is no value set?

      • Miguel Angel Goicochea

        when I clicked the “Save” button ModelState.IsValid returns true even though the FormId drop down current value is “null” (and it is set as null in the model when I receive it in the controller)

        • http://ivanz.com/ Ivan Zlatev

          What is the definition of FormId? Is it of type “int?” (nullable int)? [Required] just checks if the value is != null which is always the case with a plain int.

          • Miguel Angel Goicochea

            Hi Ivan I already solved the problem it was actually a problem in the GetHashCode() of the collection that was throwing a null pointer. Sorry for all the hassle.

            However, reading the code of the example there is something I saw in it which I found interesting. I see you are using a static attribute in the controller to store the current user. What’s the lifecycle of this value? Will it be stored until the controller class is cleaned by the garbage collector?

            And finally, you told me that the client validation won’t be used in collections because it does not handle dom changes, but is there a way to call it manually like

            $(“#submitButtton”).click(function() { callValidations(); });

          • http://ivanz.com/ Ivan Zlatev

            Hi,

            The static collection is just a quick in-memory data hack to save me time adding a data layer as it would be irrelevant to the sample. The lifetime of the variable is just during the runtime of the asp.net web server. 

            Yeah, plain jQuery Validation client validation works.

          • http://ivanz.com/ Ivan Zlatev

            Hi,

            The static collection is just a quick in-memory data hack to save me time adding a data layer as it would be irrelevant to the sample. The lifetime of the variable is just during the runtime of the asp.net web server. 

            Yeah, plain jQuery Validation client validation works.

  • dtwforme701

    hell thanks for  your example first of all .i am having a problen with calidations when uing dropdowns ,here is my code.
    formid is marked as reauired ,but the calidation is not fired,i tried the same using a texbos and it does work,what coule be the prblem here?
    thanks ! welcome to china!

  • dtwforme701

    hell thanks for  your example first of all .i am having a problen with calidations when uing dropdowns ,here is my code.
    formid is marked as reauired ,but the calidation is not fired,i tried the same using a texbos and it does work,what coule be the prblem here?
    thanks ! welcome to china!

  • Patric Forsgard

    Nice article series, your example works like a charm.

    I added a new helper-method to make it more easy to render collection items and you don’t need to specify the foreach and RenderPartial for each item in the collection, you only have to write

    @{ Html.RenderPartialCollection(model => model.FavouriteMovies, “MovieEntryEditor”); }

    And the helper method will be the following.

            public static void RenderPartialCollection(this HtmlHelper htmlHelper, Expression<Func> expression, string partialViewName)
                where TProperty : IEnumerable
            {
                var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
                var items = (IEnumerable)metadata.Model;
                if (items != null)
                {
                    foreach (var item in items)
                    {
                        using (htmlHelper.BeginCollectionItem(metadata.GetDisplayName()))
                        {
                            htmlHelper.RenderPartial(partialViewName, item);
                        }
                    }
                }
            }

    Do you see any risks to use that instead?

    • http://ivanz.com/ Ivan Zlatev

      Nice, I like this, but you will need to use metadata.PropertyName instead of metadata.GetDisplayName() as the latter will take into account the DisplayNameAttribute which is for the labels not the input name. Other than that I don’t think there are other issues.

  • Henry

    great article, thanks a lot!
    only one minor improvement, : ie seems caching the ajax get when the parameter is the same. this cause the duplicated guid problems.
    if you modify the ajax calls to:

    var tsTimeStamp= new Date().getTime();
    $.get(” …. “, { action: “get”, time: tsTimeStamp} , function(data) { 

    or maybe using a post action instead of get?

    it will solve the caching issue.

    • http://ivanz.com/ Ivan Zlatev

      Thanks for the thanks. Pretty much all jQuery framework can do that for you. For example in jQuery there is a “cache: false” that you can set as a parameter either for a single request or you can use ajaxSetup. In Dojo it’s called preventCache. Better use that than roll your own :)

  • http://twitter.com/lankaapura Sampath Lankapura

    Thanks for the nice article.

  • Sushil Sharma

    Thank you very much for such a nice article.

    What should be done if we need to add cast for the movies and give their rating. I have attached a screen shot.
    Can you please me to solve the problem
    Thank you very much…
    sushil

    • Sam

      This is a good article, thank you for it.
      I am facing the same problem as Sushil, I am trying to nest a variable length collection in another.
      for some reason it won’t work. Is there an extra step we need to take?
      Thank you.
      Sam

      • http://ivanz.com/ Ivan Zlatev

        There are two ways to do it: 1) Completely on the client-side with jQuery Tempaltes or even better – Knockout JS and submitting the form as JSON. That means however you won’t be able to use any of the ASP.NET MVC templating or validation goodies or 2) Slightly more complex setup using partial views.
        1) Is cleanest
        2) Is best if you want to use ASP.NET MVC facilities.

        I need to cleanup my html helpers and might post another blog on how to do it in way 2)

        • Betty

          Any closer to blogging about nested templates? I found a solution but it isn’t particularly tidy.

          • http://ivanz.com/ Ivan Zlatev

            Very busy lately, but to be honest I’ve done it both ways – on the client side and with server partial callbacks and neither is very clean approach.

            I have created a HTML helper to deal with each case. Will clean it up and share when I can.

  • Dmitrief

    Thanks for the post. It works great until you use a DropDownListFor in your MovieEntryEditor partial view. If Model is invalid after submission of the form all the drop down lists will lose the value that was selected. However, all regular text boxes will be fine. I’m not sure what could be the issue. Any help is appreciated!

    • Dmitrief

      Found what was wrong. You have a small error in your write up. When you say:
      Let’s replace in Html.BeginCollectionItem this line:string itemIndex = Guid.New().ToString();with:string itemIndex = GetCollectionItemIndex(collectionIndexFieldName);You forgot to say where you got variable: collectionIndexFieldName, which should be this code:string collectionIndexFieldName = String.Format(“{0}.Index”, collectionName);

  • Max

    Inserting of hidden input may lead to invalid HTML markup depending on outer context. For example, this helper will produce invalid markup for partial view consisting of tag content

    • Max

       *…consisting of ‘tr’ tag content

  • steeze

    Thanks for the great article, just to share I was able to get unobtrusive jquery validation to work in combination with your example by following http://craftingsoft.wordpress.com/2011/04/23/applying-unobtrusive-jquery-validation-to-dynamic-content-in-asp-net-mvc/

  • http://twitter.com/daniiel Daniel Velazquez

    Just what I was looking for! Excellent work!

  • Sergio Silveira

    You saved my life! I’m developing again for the web after about 6 years in the backend and services. And I was searching how to do this for hours!!! Thank you Ivan!

  • Maxim Yefremov

    Thanx for great post. In part 1 I changed Html.BeginForm to Ajax.BeginForm and added @Scripts.Render(“~/bundles/jqueryval”).

    So now validation is working without reloading page, but it’s not working for new added items. How to fix it?

  • http://twitter.com/aaron_hoffman Aaron Hoffman

    I would like to add a div to each li element to show/hide additional information if a button is clicked. I would like each div to have an id that contains the current index. How can I reference the current index in my .cshtml markup as the HtmlHelper methods do?

    ex:

    show/hide me

  • Jowen

    Thank you for this great article, it helped me a lot!

  • chika

    nice and greate article for the master detail senario.my concern is serucrity case because any one can chnage the data using firebug.how to overcome this problem

    • http://ivanz.com/ Ivan Zlatev

      You have to validate on the server side

  • Rafael Ribeiro

    Nice article.
    But I have a problem…your sample works fine..and I adapted it in my project…the Remove button is working fine…but the “Add another” button is not working…it doesn’t call the partial view in controller.
    I’m using class of virtual collection.
    Could you support me?

  • Brian Watts

    When I model any business object, I always ensure it has a unique ToString override (which helps me create the equality overrides (Equals / GetHashString). Wouldn’t this be the preferrable value to use in the hidden Index field? E.g. calling CustomerOrderLineModelObj.ToString() might yeild, “Order 790 – Sneakers, SKU: 7123″ – as there’s always situations where you have to pick it from a collection of things and you need it to ‘tell on itself’ – when I make this a practice, later needs for sorting and object identity (and MVC collection binding) already have a well-known way of discovering object identity.

  • chaaki

    I used your example. I have an issue when two new rows added, both rows contain the first row data…… This is applicable for normal edit and j query edit. Any idea why?

  • shahnaitik

    Very Nice article.
    Your sample code works fine and I have tried it in my project but the “Add another” button is not working. It doesn’t call the partial view in controller. can you please help