I'm building a simple ASP.NET MVC 5.2.3.0 view that displays a user name and a checkbox list of roles. You can check or uncheck the checkboxes to control which roles the user is in. Here is my ViewModel:
public class EditUserVM
{
public string Id { get; set; }
public string UserName { get; set; }
public List<UserRoleVM> Roles { get; set; }
}
public class UserRoleVM
{
public string RoleId { get; set; }
public string RoleName { get; set; }
public bool IsMember { get; set; }
}
And it's displayed like this:
@model VMContainer<EditUserVM>
@* omitted for exmaple *@
var userVM = Model.ViewModel;
<div class="panel panel-primary">
<div class="panel-heading"><h4 style="display:inline">User: @userVM.UserName</h4></div>
<div class="panel-body">
@Html.ValidationSummary(false, "", new { @class = "text-danger" })
<h4>Users Roles</h4>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
@Html.HiddenFor(m => userVM.Id)
@Html.HiddenFor(m => userVM.UserName)
<table class="table table-bordered table-striped">
<thead><tr><th></th><th>Role</th></tr></thead>
<tbody>
@for (int x = 0; x<userVM.Roles.Count; x++){
<tr>
<td>
@Html.HiddenFor(m => userVM.Roles[x].RoleId)
@Html.CheckBoxFor(m => userVM.Roles[x].IsMember)
</td>
<td>@Html.DisplayFor(m => userVM.Roles[x].RoleName)</td>
</tr>
}
</tbody>
</table>
<div>
<input type="submit" value="Save" class="btn btn-success" />
@Html.ActionLink("Cancel", "Users", null, new { @class = "btn btn-default" })
</div>
</div>
}
Notice the model type is VMContainer. This is a wrapper used by my views so that in case there is an error generating the inner T ViewModel, it can display an error instead of the normal view content. To make it easier to access the EditUserVM and because that is all I need to submit, not the VMContainer wrapper, I assign it to a local variable var userVM = Model.ViewModel;
. I've used this pattern a lot in previous projects. The problem is when I inspect the page in developer tools, all of my inputs are named wrong. They all get a prefix for what looks like a generated class. For example, @Html.HiddenFor(m => userVM.Id)
renders:
Through trial and error, I narrowed this down to not happening if I remove the for
loop. That is with the for
loop commented out, the remaining inputs are named correctly. Through further trial and error I discovered that what really triggers it is accessing the Roles indexer using a variable. E.G. If I remove the loop and just do this, it produces the wrong name for all of the inputs:
@Html.HiddenFor(m => userVM.Id)
@Html.HiddenFor(m => userVM.UserName)
@{ int x = 0; }
@Html.HiddenFor(m => userVM.Roles[x].RoleId);
It does not generate the wrong names if I use the constant [0]
in place of [x]
or m.ViewModel.Roles[x]
instead of userVM.Roles[x]
. So to try to spell it out completely, it seems to occur when accessing the indexer of a local variable's descendant using a variable for the index parameter.
I found this or something similar acknowledged for MVC 6 (at the time) but I don't understand the problem. I'm a little surprised this still exists if it is in fact a bug. Although it's hard to describe, it doesn't seem like it would be a totally uncommon scenario. Can someone explain what is going on and hopefully offer a fix? I prefer to only post an EditUserVM to my action when the form submits.