1

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?

erictrigo
  • 989
  • 2
  • 13
  • 41
  • I would suggest that your design is not a good user experience. You can only ever add/delete one role at a time and involves multiple redirects that degrade performance. Generating a checkboxlist of all roles (with the current user roles checked) would be a better user experience and less code. Refer [this answer](http://stackoverflow.com/questions/29542107/pass-list-of-checkboxes-into-view-and-pull-out-ienumerable/29554416#29554416) for an example of how you might implement it –  Oct 06 '15 at 23:03

3 Answers3

2

It looks like the second method, for removals, is missing a line which re-initializes model object with roles list. Since ASP.NET MVC is stateless, this re-initialization has to be done on each request, even if returned view is the same.

Add this line:

vm.AssignedRoles = assignedRoles.OrderBy(r => r.Name).Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();

right after foreach ends in RemoveRole and the error should go away.

Andrei
  • 55,890
  • 9
  • 87
  • 108
0

The problem is you've never assigned the assignedRoles collection to your view property (AssignedRoles).

Also, you can make your code cleaner by using a LINQ statement to create your list.

0

In your GET action, you fill up the AssignedRoles property of your view model. In the POST action you fail to do that. So when it gets to the view code it is null and the helper can't make a dropdown list out of a null collection. You need to populate that in the POST action. (Note that it doesn't stay populated between the GET and POST actions.)

Becuzz
  • 6,846
  • 26
  • 39