Scenario: I have a table with rows that are populated from a ViewModel. There are checkboxes for each row that allow the user to check 1 or more of the rows and then choose from actions in a dropdown menu to make edits to properties on the selected rows.
Everything works fine to this point, and I can get the ViewModel to pass correctly and then use it and all it's properties in a POST Action method. I could make the changes based on the option the user picked.
However, since some of the options in the dropdown would make fairly substantial and irreversible changes, I am calling a new View with a GET and populating a new table with just the selected rows, and asking the user to confirm they want to make the changes. Everything is still good up to this point. The new View populates as expected with only the rows that were selected in the previous View.
Problem: After the user confirms their intent, an Action method is called with POST. The ViewModel that correctly populated the current View is making its way into the controller correctly. I get the ViewModel, but not with the same properties as the one that populated the View.
ViewModel
public class ProjectIndexViewModel
{
public List<ProjectDetailsViewModel> Projects { get; set; }
public string FlagFormEditProjects { get; set; }
public string FlagFormNewProjectStatus { get; set; }
}
The List<ProjectDetailsViewModel> Projects
is what is used to populate the rows in the table, and Projects are what are not binding correctly in the POST Action methods in the controller.
Initial View where the checkboxes are selected. Note the example of one of the javascript functions that is called when one of the dropdown options is selected, which is what submits the form.
@using (Html.BeginForm("EditProjectsTable", "Project", FormMethod.Get, new { name = "formEditProjects", id = "formEditProjects" }))
{
@Html.HiddenFor(item => item.FlagFormEditProjects)
@Html.HiddenFor(item => item.FlagFormNewProjectStatus)
....
<table>
<thead>
....
</thead>
<tbody>
@for (int i = 0; i < Model.Projects.Count; i++)
{
<tr>
<td>@Html.DisplayFor(x => x.Projects[i].ProjectNumber)</td>
<td>@Html.DisplayFor(x => x.Projects[i].ProjectWorkType)</td>
.... // more display properties
<td>
@Html.CheckBoxFor(x => x.Projects[i].Selected, new { @class = "big-checkbox" })
@Html.HiddenFor(x => x.Projects[i].ProjectModelId)
</td>
</tr>
}
</tbody>
</table>
}
function submitFormRemoveProjects() {
$("#FlagFormEditProjects").attr({
"value": "RemoveProjects"
});
$('#formEditProjects').submit();
}
Action method that returns the "confirmation" View (works fine)
[HttpGet]
[Authorize(Roles = "Sys Admin, Account Admin, User")]
public async Task<ActionResult> EditProjectsTable([Bind(Include = "Projects,FlagFormEditProjects,FlagformNewProjectStatus")]ProjectIndexViewModel projectIndexViewModel)
{
// Repopulate the Projects collection of ProjectIndexViewModel to
// include only those that have been selected
return View(projectIndexViewModel);
}
View that is returned from Action method above (works fine) Note that the Action method that gets called is set dynamically with the actionName variable in the Html.BeginForm call.
@using (Html.BeginForm(actionName, "Project", FormMethod.Post))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(model => model.FlagFormNewProjectStatus)
....
<table>
<thead>
....
</thead>
<tbody>
@for (int i = 0; i < Model.Projects.Count; i++)
{
<tr>
<td>@Html.HiddenFor(x => x.Projects[i].ProjectModelId)</td>
<td>@Html.DisplayFor(x => x.Projects[i].ProjectNumber)</td>
<td>@Html.DisplayFor(x => x.Projects[i].ProjectWorkType)</td>
.... // more display properties
</tr>
}
</tbody>
</table>
<input type="submit" value="Delete Permanently" />
}
An example of one of the Controller Action methods that is called from this View, and that does not have the same Project that was in the View. Somehow, it has the same number of Projects that were originally selected, but if only one was selected, it has the Project with the lowest Model Id. I'm not sure how else to describe what's happening. But in summary, the correct ViewModel is not making it's way into the POST method example shown below.
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Sys Admin, Account Admin")]
public async Task<ActionResult> DeleteConfirmedMultipleProjects([Bind(Include = "Projects")] ProjectIndexViewModel projectIndexViewModel)
{
if (ModelState.IsValid)
{
// Remove Projects from db and save changes
return RedirectToAction("../Project/Index");
}
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Please help!