You can refer this post. It works perfect for me.
http://ivanz.com/2011/06/16/editing-variable-length-reorderable-collections-in-asp-net-mvc-part-1/
I will quote it below:
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.
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.
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>
}
he 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?
We can specify any index value we want
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.Html