0

I am doing user roles in MVC6 (using EF7), and I am think I am just missing something, but on a form used to define a user roles I get nothing in the posted back Model to the controller

This is my Model

public class UserRoleItem
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public IdentityUserRole<int> userRole { get; set; }
    public bool HasRole { get; set; }
}

public class UsersRolesViewModel
{
    public int UserId { get; set; }
    public string FullName { get; internal set; }

    public List<UserRoleItem> UserRoles;
}

This is the view

@model Skill.ViewModels.Manage.UsersRolesViewModel
<br />
<h3>Set Roles for user - @Model.FullName</h3>
<form asp-action="ChangeUsersRoles" >
    <div class="form-horizontal">
        <hr />
        <input type="hidden" asp-for="UserId" />

        @foreach (var userRole in Model.UserRoles)
        {
            <input type="hidden" asp-for="@userRole.Id" />
            <div class="form-group">
                <div class="inline-block col-md-8">
                    <span class="col-md-1" align="center">
                        <input type="checkbox" asp-for="@userRole.HasRole" />
                    </span>
                    <div class="col-md-7">
                        @userRole.Name (@userRole.Description)
                    </div>
                </div>
            </div>
        }
        <hr />
        <div class="form-group">
            <div class="col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
</form>

This is the Controller

// GET: Users/ChangeUserRoles/5
public async Task<IActionResult> ChangeUserRoles(int? id)
{
    if (id == null)
    {
        return HttpNotFound();
    }

    var model = new UsersRolesViewModel();
    ApplicationUser appUser = await _context.ApplicationUsers.SingleAsync(m => m.Id == id);
    if (appUser == null)
    {
        return HttpNotFound();
    }
    else
    {
        model.UserId = (int)id;
        model.FullName = appUser.FullName;
        var some = from r in _context.Roles
                   from ur in _context.UserRoles
                   .Where(inner => r.Id == inner.RoleId && inner.UserId == id)
                   .DefaultIfEmpty()
                   select new UserRoleItem
                   {
                       Id = (int)r.Id,
                       Name = r.Name,
                       Description = r.NormalizedName,
                       userRole = ur, // this is needed else it has a hissy fit
                       HasRole = (ur != null)
                   };
        // get all of the Roles and then also link to the ones the user currently has
        model.UserRoles = (some).ToList();
    }
    return View(model);
}

// POST: Users/ChangeUserRoles/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangeUserRoles([Bind(include:"UserId,UserRoles")]UsersRolesViewModel userModel)
{
    if (ModelState.IsValid)
    {
        // update based on the changes

        // return RedirectToAction("Edit", new { userModel.UserId });
    }
    return View(userModel);
}

So when I get the post back on Save, the UserRoles list is null, so I assume I am just missing an obvious thing here?

Also another small issue is the EF Linq statement. If I remove the

userRole = ur,

statement from the Select portion of the Linq query, the system has a fit and says my schema is out of date (which it isn't). I think it is due to the following statement where I am testing the outer join value

HasRole = (ur != null)

Although this seems perfectly reasonable and works if the ur variable is used prior to testing for null (or not)

Nick De Beer
  • 5,232
  • 6
  • 35
  • 50
Grant Nilsson
  • 565
  • 5
  • 18
  • You cannot use a `foreach` loop to generate form controls for a collection (if you inspect the html, you will see that your inputs all have the same `name` attribute that have no relationship to you model). Use a `for` loop or an `EditorTemplate` for `UserRoleItem` to generate the correct `name` attributes with indexers. For an example using a `for` loop, refer [this answer](http://stackoverflow.com/questions/29542107/pass-list-of-checkboxes-into-view-and-pull-out-ienumerable/29554416#29554416) –  Jan 27 '16 at 00:11
  • Thanks Stephen. After I entered the issue I actually changed the code to use EditorTemplates and then the array of data was correctly generated. Maybe I should upload the solution for posterity? – Grant Nilsson Jan 27 '16 at 00:16
  • Add you own answer and accept it to close this out :) –  Jan 27 '16 at 00:19

1 Answers1

0

After entering this question - I further investigated the issue and found that I could do what I needed using an EditorTemplate So I created the EditorTemplate Folder under my Users View folder as it was a UsersController and then added the following UserRoleItem.cshtml file

@model UserRoleItem

<input type="hidden" asp-for="Id" />
<div class="form-group">
    <div class="inline-block col-md-8">
        <span class="col-md-1" align="center">
            <input type="checkbox" asp-for="HasRole" />
        </span>
        <div class="col-md-7">
            @Model.Name (@Model.Description)
        </div>
    </div>
</div>

I then changed the view (called ChangeUsersRole.cshtml) to be

@model Skill.ViewModels.Manage.UsersRolesViewModel

<br />
<h3>Set Roles for user - @Model.FullName</h3>
<hr />
<form asp-action="ChangeUsersRoles">
    <div class="form-horizontal">
        <input type="hidden" asp-for="UserId" />
        @Html.EditorFor(m => m.UserRoles)
        <hr />
        <div class="form-group">
            <div class="col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
</form>

Note also: that I tried using the new format

<input asp-for="UserRoles" />

instead of this line in the view

@Html.EditorFor(m => m.UserRoles)

But it did not work as expected. Again maybe I am still doing something wrong - or maybe that feature isn't working yet?

Grant Nilsson
  • 565
  • 5
  • 18