2

I am using Editortemplates to show a list of items associated with a form and i allow user to remove row if not wanted But when uiser submits the form after removing any row on POST action i am getting collection as null and if the user did'nt do any deletions of items the i get the complete collection at POST action with modified values . Here is the code i tried with Models Controller and View

Models

public class Student
{
    public string StudName { get; set; }
    public int StudId { get; set; }
    public List<Assignment> Assignments { get; set; }
}
public class Assignment 
{
    public int Id{get;set;}
    public string Name{get;set;}
    public int Amount{get;set;}
    public string Remarks{get;set;}
}

Controller Actions

public ActionResult Assign(int Id)
{
    Student model = new Student { StudId = Id, StudName = "Name +" + Id };
    List<Assignment> _assignments = new List<Assignment>();
    for(int j=0;j<3;j++)
    {

        Assignment itm = new Assignment { Id = j, Name = "Assign " + j, Remarks = "Remarks " + j, Amount = j * 33 };
        _assignments.Add(itm);
    }
    model.Assignments = _assignments;
    return View(model);
}
[HttpPost]
public ActionResult Assign(int Id,Student model)
{
    return Content("rxvd "+model.Assignments.Count);
    //count is correct if not removing anything from Assign page 
    //If i remove any items from assign page then count is null
}

Assign View

@model OpenXMLMvc3.Controllers.Student
@{
    ViewBag.Title = "Assign";
}
<h2>Assign</h2>
<script type="text/javascript">
$(function () {
//call from Editor template
    $("a.deleteRow").live("click", function () {
        $(this).parents("tr.editorRow:first").remove();
        return false;
    });
});
</script>
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
        <div class="col-md-6">
        <div class="editor-label">
            @Html.LabelFor(model => model.StudName)
            @Html.EditorFor(model => model.StudName)
            @Html.ValidationMessageFor(model => model.StudName)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.StudId)
            @Html.EditorFor(model => model.StudId)
            @Html.ValidationMessageFor(model => model.StudId)
        </div>
        </div>
       <div class="col-md-6">
        <table  id="container">
            @for (int j = 0; j < Model.Assignments.Count; j++)
            {
            //using editor template to list the assignments 
                 @Html.EditorFor(model => Model.Assignments[j])
            } 
    @*This will [using foreach instead of for ] result a NULL at form submission even without editing or removing any rows 
        @foreach (OpenXMLMvc3.Controllers.Assignment assign in Model.Assignments)
        {
                @Html.EditorFor(model => assign)
        }*@
        </table>
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
}

Editor template view

@model OpenXMLMvc3.Controllers.Assignment

<tr class="editorRow">
<td>
  @Html.HiddenFor(model => Model.Id, new {@class="iHidden" })
  sa
</td>
<td>
    @Html.EditorFor(model => Model.Name)
</td>
<td>
     @Html.EditorFor(model => Model.Amount)
</td>
    <td>
      @Html.EditorFor(model => Model.Remarks)
    </td>
    <td>
   <a href="#" class="deleteRow">delete</a>
   </td>
 </tr>  

So how can i allow user to remove item from Collection via EditorTemplates in MVC3

Sebastian
  • 4,625
  • 17
  • 76
  • 145

2 Answers2

4

By default the DefaultModelBinder will only bind collection that are zero based and consecutive. If you delete the first item (with index = 0) then no items will be bound. You need to include special hidden input in the view that the DefaultModelBinder uses to match up non-consecutive indexers. Unfortunately you will not be able to use an EditorTemplate in this case and instead you need to use a for loop inside the main view (but as an alternative you can use the BeginCollectionItem helper as an option to using a for loop).

Change the main view to

<table  id="container">
  @for (int j = 0; j < Model.Assignments.Count; j++)
  {
    <tr class="editorRow">
      <td>
        @Html.HiddenFor(m => m.Assignments[j].Id, new {@class="iHidden" })
        sa
        <input type="hidden" name="Assignments.Index" value="@j" /> // add this
      </td>
      <td>@Html.EditorFor(m => m.Assignments[j].Name)</td>
      <td>@Html.EditorFor(m => m.Assignments[j].Amount)</td>
      <td>@Html.EditorFor(m => m.Assignments[j].Remarks)</td>
      <td><a href="#" class="deleteRow">delete</a></td>
    </tr>
  }
</table>

Side note: EditorFor() accepts IEnumerable<T> so the correct usage in your previous attempt should have been just

<table  id="container">
  @Html.EditorFor(model => Model.Assignments)
</table>

i.e. not in a loop - the EditorFor() method is smart enough to generate the correct html for each item in your collection based on the template

Also, I recommend not using model => Model.XXX (capital 'M'). It can throw an exception under some conditions (and in any case its simpler to use just m => m.XXX )

Note also the BeginCollectionItem helper mentioned above just adds this hidden input automatically and changes the zero based indexer to a Guid so the generated html is

<input type="hidden" name="Assignments[aaaef973-d8ce-4c92-95b4-3635bb2d42d5].ID" .... />
<input type="hidden" name="Assignments.Index" value="aaaef973-d8ce-4c92-95b4-3635bb2d42d5" />

however it comes into its own if your also dynamically adding new items to the collection in the view

  • Yes i have to support the option for Add new item as well :) So the recommendation is to use BeginCollectionItem helper ? – Sebastian Jul 24 '15 at 04:36
  • Using `BeginCollectionItem` means each time you want to add a new item you need to use ajax to call a method that returns a partial view (i.e what was previously your editor template) which means a minor performance hit. A pure client side option is shown in the answers [here](http://stackoverflow.com/questions/29837547/set-class-validation-for-dynamic-textbox-in-a-table/29838689#29838689) and [here](http://stackoverflow.com/questions/28019793/submit-same-partial-view-called-multiple-times-data-to-controller/28081308#28081308) –  Jul 24 '15 at 04:41
  • The client side option gives better performance obviously but a bit harder to maintain (if you ever change a property name, or add one or even change a validation attribute you also need to update the html template inside the view) –  Jul 24 '15 at 04:41
  • Also refer [this article](http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/) for usage the `BeginCollectionItem()` method –  Jul 24 '15 at 04:50
  • A complete tutorial based on all these blog post is there to helps a lot to understood & solve pblm http://justmycode.blogspot.in/2012/07/learning-mvc-editing-variable-length.html – Sebastian Jul 24 '15 at 07:25
0

I have add and delete options using Editor Template and for each action, I simply post back the collection to the server using Ajax, add/remove from the collection, re-render using the Editor Template and then replace the HTML.

This approach maintains the indexes of the collection and ensures the model binding still works.

Just an alternative to the above answer that may be useful to someone.

Ben D
  • 669
  • 1
  • 11
  • 19