asp.net-mvcasp.net-ajaxajax.beginform

MVC Ajax Form inside a For Loop


I am using an Ajax.BeginForm inside a For Loop with allows me to post to the controller for each item in a the for loop. Initially the for loop renders each item correctly on page load. When the controller has handled the incoming View Model which is populated correctly from the Ajax.BeginForm, the method sends back a newly generated view model back to the View, however each item in my For loop is now duplicated, and all the textboxes now have the value of the submitted View Model.

Very confused how to structure this code correctly, I need the submission to work based on ajax and partial view. I did look at using JQuery to submit, but was concerned I may lose the AntiForgeryToken and that the Ajax.BeginForm was a more elegant approach. Any help greatly appreciated. Happy to provide more information also.

VIEW

@model Namespace.Models.MyParentViewModel
@for (int i = 0; i < Model.Items.Count; i++)
{
    using (Ajax.BeginForm("SaveItem", "Controller", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "pensions" }))
    {
      @Html.AntiForgeryToken()
      @Html.ValidationSummary()
      <div>
         @Html.Hidden("ID", Model.Items[i].ID)
         @Html.TextBox("TheName", Model.Items[i].TheName, new { @class = "form-control", @id = "item-" + i})
         <input type="submit" value="Save" class="btn save" name="Command" />
      </div>
    }
}

CONTROLLER

[HttpPost]
[ValidateAntiForgeryToken]
[ActionName("SaveItem")]
public ActionResult SaveItem(MyItemViewModel mivm, string Command)
{
   if (ModelState.IsValid)
   {
       #do some logic
   }

   // Return a newly populated MyViewModel with updated mivm.
   var model = PopulateMyParentViewModel();
   return PartialView("_MyPartialView", model);
}

private MyParentViewModel PopulateMyParentViewModel()
{
   List<MyItemsViewModel> lstItems = new List<MyItemsViewModel>();
   foreach (var item in enity.items.OrderBy(p => p.ID).ToList())
   {
     var ExistingItem = new MyItemViewModel
     {
        ID = item.ID,
         TheName = item.TheName
     };

     lstItems.Add(MyItemViewModel);
   }
   MyParentViewModel.Items = lstItems;
   return MyParentViewModel
}

Solution

  • I am not sure what is "pensions" in your code, I guess it is a div surrounding every form generated by the loop? If so your problem is the following:

    Solution

    --------- Update -----------

    To answer your comment:

    Assume you have a view which - for the sake of simplicity - displays records in a tabular format. Then, the result looks like this:

    Name   | Age | Salary | Hide
    ------------------------------------
    Peter  |  32 |   $15k | Hide button
    Eva    |  28 |   $12k | Hide button
    

    Assume what you change with an Ajax request is that whenever you hide a record, you send back a single table row with a button that now displays 'show' instead of 'hide' and the same data about the person which belongs to the button that triggered the request. Then, when you make a direct (=non-ajax, when you directly navigate to the page) request, you do this:

    @model PeopleCollection
    ...
    <table>
    <thead>
      <tr>
        <td>Name</td>
        <td>Age</td>
        <td>Salary</td>
        <td>Hide</td>
      </tr>
    </thead>
    <tbody>
      @foreach (Person record in Model) {
        @Html.Partial("TableRow_Partial",record)
      }
    </tbody>
    

    The "TableRow_Partial" view looks like this:

    @model Person
    @{
      AjaxOptions opts = new AjaxOptions() {
        UpdateTargetId = "person-row-" + Model.Id.ToString(),
        InsertionMode = InsertionMode.ReplaceWith
        // etc
      };
    }
    ...
    <tr id="person-row-@Model.Id">
      <td>@Model.Name</td>
      <td>@Model.Age</td>
      <td>@Model.Salary</td>
      <td>
        <!-- configure the BeginForm(..) call with the action name and a routeValue anonymous object as "new { id = Model.Id}" -->
        @using (Ajax.BeginForm(...)) {
          <input type="submit" value="@(Model.IsHidden ? "Show" : "Hide")" />
        }
      </td>
    </tr>
    

    This will simply extract the construction of loops into a separated partial view. The action method procession your Ajax-request will simply return this view, filled with the one single record you have updated, like so:

    public ActionResult UpdateRecord(Int64 id)
    {
      var _record = Repository.Get(id);
      // logic to hide and update record
      return PartialView("TableRow_Partial",_record);
    }
    

    For this to work, you have to set InsertionMode to InsertionMode.ReplaceWith. All this does is that when you send an ajax request to do something with just one record, you only refresh one item - the one that triggered the ajax call, and then you simply return the new row (One row!) and replace the old one with this new one.

    If you really want to stick to sending the whole collection of items back to the client every time you do something with the records, just send the entire rendered table to the client and replace the old one with the new one in a similar manner.