I have two forms: one for assigning roles to users and the other one for removing roles from users. They're strikingly similar, both the views and the controllers. Here they are (the form itself):
AssignRole.cshtml
@using (Html.BeginForm("AssignRole", "User", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(m => m.UserID)
<div class="form-group">
@Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.UserName, new { @class = "form-control", @readonly = "readonly" })
@Html.ValidationMessageFor(m => m.UserName, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.RoleName, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.DropDownListFor(m => m.RoleName, new SelectList(Model.UnassignedRoles, "Value", "Text"), Resources.DropdownSelect, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="@Resources.Assign" class="btn btn-default" />
</div>
</div>
}
RemoveRole.cshtml
@using (Html.BeginForm("RemoveRole", "User", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(m => m.UserID)
<div class="form-group">
@Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.UserName, new { @class = "form-control", @readonly = "readonly" })
@Html.ValidationMessageFor(m => m.UserName, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.RoleName, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.DropDownListFor(m => m.RoleName, new SelectList(Model.AssignedRoles, "Value", "Text"), Resources.DropdownSelect, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="@Resources.Remove" class="btn btn-default" />
</div>
</div>
}
Finally, here's the controller with the actions they make use of:
UserController.cs
//
// GET: /User/AssignRole
[Authorize(Roles = "Admin")]
[HttpGet]
public ActionResult AssignRole(string userID)
{
var user = context.Users.Where(u => u.Id == userID).FirstOrDefault();
var vm = new UserAssignRoleViewModel();
vm.UserID = user.Id;
vm.UserName = user.UserName;
List<IdentityRole> unassignedRoles = new List<IdentityRole>();
foreach (var role in context.Roles)
{
if (this.UserManager.IsInRole(vm.UserID, role.Name) == false)
{
unassignedRoles.Add(role);
}
}
vm.UnassignedRoles = unassignedRoles.OrderBy(r => r.Name).Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
return View(vm);
}
//
// POST: /User/AssignRole
[Authorize(Roles = "Admin")]
[HttpPost]
public ActionResult AssignRole(UserAssignRoleViewModel vm)
{
this.UserManager.AddToRole(vm.UserID, vm.RoleName);
ViewBag.ResultMessage = Resources.RoleAssignedSuccessfully;
List<IdentityRole> unassignedRoles = new List<IdentityRole>();
foreach (var role in context.Roles)
{
if (this.UserManager.IsInRole(vm.UserID, role.Name) == false)
{
unassignedRoles.Add(role);
}
}
vm.UnassignedRoles = unassignedRoles.OrderBy(r => r.Name).Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
return View(vm);
}
//
// GET: /User/RemoveRole
[Authorize(Roles = "Admin")]
[HttpGet]
public ActionResult RemoveRole(string userID)
{
var user = context.Users.Where(u => u.Id == userID).FirstOrDefault();
var vm = new UserRemoveRoleViewModel();
vm.UserID = user.Id;
vm.UserName = user.UserName;
vm.AssignedRoles = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
List<IdentityRole> assignedRoles = new List<IdentityRole>();
foreach (var role in context.Roles)
{
if (this.UserManager.IsInRole(vm.UserID, role.Name) == true)
{
assignedRoles.Add(role);
}
}
vm.AssignedRoles = assignedRoles.OrderBy(r => r.Name).Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
return View(vm);
}
//
// POST: /User/RemoveRole
[Authorize(Roles = "Admin")]
[HttpPost]
public ActionResult RemoveRole(UserRemoveRoleViewModel vm)
{
if (this.UserManager.IsInRole(vm.UserID, vm.RoleName))
{
this.UserManager.RemoveFromRole(vm.UserID, vm.RoleName);
ViewBag.ResultMessage = Resources.RoleUnassignedSuccessfully;
List<IdentityRole> assignedRoles = new List<IdentityRole>();
foreach (var role in context.Roles)
{
if (this.UserManager.IsInRole(vm.UserID, role.Name) == true)
{
assignedRoles.Add(role);
}
}
}
else
{
ViewBag.ResultMessage = Resources.ThisUserDoesNotBelongToSelectedRole;
}
return View (vm);
}
Here's the issue:
The dropdown has to get repopulated everytime, either assigning roles to users or removing them. Everything works fine in the assigning roles side; it only shows the unassigned roles in the dropdown, and when you add a role, coming back from the POST action it shows the refreshed dropdown without the role you just assigned.
But in the removing roles side, as soon as you remove a role from a user (which it DOES correctly), coming back to the view from the POST action it throws the exception
Value cannot be null. Parameter name: items
in the line
@Html.DropDownListFor(m => m.RoleName, new SelectList(Model.AssignedRoles, "Value", "Text"), Resources.DropdownSelect, new { @class = "form-control" })
My guess is that since in the POST Action method RemoveRole
I'm not changing in any way the RoleName
property of the UserRemoveRoleViewModel
, and coming back to the view the dropdown has been repopulated, the m => m.RoleName
crashes because it's looking for the already removed role, which is not in the list anymore. Hope I'm explaining myself well enough.
The problem is that I have absolutely no idea on how to fix this. Any help?