0

I am attempting to set up a PartialView that displays a list of objects, which contain a boolean property that the end user will check off. Upon submitting, the PartialView should perform an AJAX POST in order to pass that list of objects to another PartialViewResult and display another PartialView on the same page.

However, my controller is getting a null value for that list. How Do I fix this?

Simple Example:

View:

<div id="divNumOne">
     <!-- Where the first PartialView is Displayed -->
</div>
<div id="divNumTwo">
     <!-- Where the second PartialView should be Displayed -->
</div>

PartialViewOne:

@model MyApplicationName.Models.SearchList

<script src="~/Scripts/jquery-3.3.1.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>

@{using (Ajax.BeginForm("PartialView2", "ControllerName", null, new AjaxOptions()
{
     HttpMethod = "POST",
     UpdateTargetId = "divNumTwo",
     InsertionMode = InsertionMode.Replace
}, new
{
     id = "partialViewOneSubmitForm"
}))
{
     @Html.AntiForgeryToken()
     <table>
          <thead>
               <tr>
                    <th>Select</th>
                    <th>ColumnOne</th>
                    <th>ColumnTwo</th>
                    <!-- etc. ... -->
               </tr>
          </thead>
          <tbody>
          @foreach (var item in Model.SearchResultList)
          {
               <tr>
                    <td>
                         @Html.CheckBoxFor(modelItem => item.Select)
                    </td>
                    <td>
                         @Html.DisplayFor(modelItem => item.ColumnOne)
                         @Html.HiddenFor(modelItem => item.ColumnOne)
                    </td>
                    <td>
                         @Html.DisplayFor(modelItem => item.ColumnTwo)
                         @Html.HiddenFor(modelItem => item.ColumnTwo)
                    </td>
                    <!-- etc. ... -->
               </tr>
          }
          </tbody>

<!-- etc. ... -->

<button type="submit" class="btn btn-outline-primary" style="font-weight:500;">Lock in</button>

Model:

public class SearchList
{
     public List<SearchResult> SearchResultList { get; set; }
}

public class SearchResult
{
     public bool Select { get; set; }
     public string ColumnOne { get; set; }
     public string ColumnTwo { get; set; }
     // etc. ...
}
C Murphy
  • 47
  • 1
  • 13

1 Answers1

1

MVC is very particular about its model binding with lists. It uses the variable names passed in the lambda expression to set as the name attribute on each form element, and then tries to match those against the model when passed back to the controller. If you inspect each element, you'll probably see name="item.Select", name="item.ColumnOne", and name="item.ColumnTwo" for every item on the list. This means the controller can't disinguish between them, so no binding is done.

The fix: use a for loop instead of a foreach loop.

@for (var i = 0; i < Model.SearchResultList.Count; i++) // might need to be .Length or .Count() depending on the type of SearchResultList
{
    <tr>
        <td>
            @Html.CheckBoxFor(modelItem => Model.SearchResultList[i].Select)
        </td>
        <td>
            @Html.DisplayFor(modelItem => Model.SearchResultList[i].ColumnOne)
            @Html.HiddenFor(modelItem => Model.SearchResultList[i].ColumnOne)
        </td>
        <td>
            @Html.DisplayFor(modelItem => Model.SearchResultList[i].ColumnTwo)
            @Html.HiddenFor(modelItem => Model.SearchResultList[i].ColumnTwo)
        </td>
        <!-- etc. ... -->
    </tr>
}

This allows the controller to correctly model bind on POST.

Nathan Miller
  • 785
  • 4
  • 11
  • I will attempt this right now. Thank you for that clarification, though. – C Murphy Dec 19 '19 at 16:29
  • For further clarification, I had actually set up mine with a foreach loop and a variable that I incremented, and manually edited the html like so: new { id = "SearchResult_" + @i + "__Select", name = "SearchResult[" + @i + "].Select" } but that didn't work. Not sure why a foreach loop wouldn't work, but a for loop does. Regardless, thank you for your timely and insightful answer @Nathan Miller ! – C Murphy Dec 19 '19 at 16:45