ASP.NET Core Identity用户编辑页回传Roles列表为空问题求助
管理员编辑用户角色时Post方法接收Roles列表为null的问题排查与解决
问题描述
开发管理员编辑用户详情(包含角色)的视图时,Post方法中接收的UserViewModel里的Roles列表始终为null,相关代码如下:
视图模型
using FNSD.Models; using System.ComponentModel.DataAnnotations; namespace FNSD.ViewModels { public class UserViewModel { public string UserId { get; set; } = string.Empty; public string Username { get; set; } = string.Empty; [Required] [EmailAddress] [Display(Name = "Email")] public string Email { get; set; } = string.Empty; public List<RoleViewModel> Roles { get; set; } [Required] [Display(Name = "User Hospital")] public int UserHospitalId { get; set; } public List<LkpHospital> Hospitals { get; set; } = new List<LkpHospital> { }; [Display(Name = "User Phone Number")] public string PhoneNumber { get; set; } = string.Empty; } public class RoleViewModel { public string RoleName { get; set; } public bool IsSelected { get; set; } } }
视图代码
<div class="row"> <div class="col-md-4"> <form id="registerForm" method="post" asp-controller="UserList" asp-action="UserEdit"> <hr /> <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> <input type="hidden" asp-for="UserId" /> <input type="hidden" asp-for="Username" /> <div class="form-group mb-3"> <input asp-for="Email" class="form-control" aria-required="true" placeholder="Please enter new email." /> <label asp-for="Email" class="form-label"></label> <span asp-validation-for="Email" class="text-danger"></span> </div> <div class="form-group mb-3"> <input asp-for="PhoneNumber" class="form-control" placeholder="Please enter phone number." /> <label asp-for="PhoneNumber" class="form-label"></label> <span asp-validation-for="PhoneNumber" class="text-danger"></span> </div> <div class="form-group mb-3"> <select asp-for="UserHospitalId" aria-required="true" class="form-control" asp-items="@(new SelectList(Model.Hospitals, "HospitalId", "Hospital"))" placeholder="role"></select> <label asp-for="UserHospitalId"></label> <span asp-validation-for="UserHospitalId" class="text-danger"></span> </div> <div class="form-group"> <label>Roles</label> @foreach (var role in Model.Roles) { <div class="form-radio"> <input type="radio" value="@role.IsSelected" asp-for="@role.IsSelected" /> <label asp-for="@role.IsSelected">@role.RoleName</label> </div> } </div> <br /> <div class="form-floating"> <a asp-controller="UserList" method="get" asp-action="UserList" class="btn btn-danger btn-block"> Cancel </a> <button type="submit" class="btn btn-primary float-end" style='margin-right:16px'>Save Changes</button> <a asp-controller="UserList" method="get" style='margin-right:16px' asp-action="ResetPassword" asp-route-userEmail="@Model.Email" class="btn btn-primary btn-block float-end"> Reset Password </a> </div> </form> </div> </div>
Controller的Get与Post方法
[HttpGet] public async Task<IActionResult> UserEdit(string userEmail) { if (string.IsNullOrEmpty(userEmail)) return NotFound(); var user = await _userManager.FindByEmailAsync(userEmail); if (user == null) return NotFound(); var userRoles = await _userManager.GetRolesAsync(user); var allRoles = _roleManager.Roles.ToList(); var viewModel = new UserViewModel() { UserId = user.Id, Username = user.UserName, Email = user.Email, PhoneNumber = user.PhoneNumber, Roles = allRoles.Select(r => new RoleViewModel { RoleName = r.Name, IsSelected = userRoles.Contains(r.Name) }).ToList(), UserHospitalId = _usersHosptialRepository.GetHospitalIdsFromUserId(user.Id).Last().id, Hospitals = _lookupRepository.EnabledHospitals().ToList(), }; return View("UserEdit", viewModel); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> UserEdit(UserViewModel viewModel) { if (ModelState.IsValid) { var user = await _userManager.FindByEmailAsync(viewModel.Email); if (user == null) return NotFound(); user.UserName = viewModel.Email; user.Email = viewModel.Email; var userRoles = await _userManager.GetRolesAsync(user); var selectedRoles = viewModel.Roles.Where(r => r.IsSelected).Select(r => r.RoleName).ToList(); var result = await _userManager.UpdateAsync(user); if (result.Succeeded) { // Remove user from roles not selected var rolesToRemove = userRoles.Except(selectedRoles).ToList(); if (rolesToRemove.Any()) { await _userManager.RemoveFromRolesAsync(user, rolesToRemove); } // Add user to roles selected var rolesToAdd = selectedRoles.Except(userRoles).ToList(); if (rolesToAdd.Any()) { await _userManager.AddToRolesAsync(user, rolesToAdd); } return RedirectToAction("Index"); // Redirect to list page or another suitable action } foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } } List<UserListViewModel> users = await _userListControllerHelper.MapToViewModel(_context); return View("UserList", users); }
问题原因
- 集合绑定格式错误:使用
@foreach遍历Model.Roles时,asp-for="@role.IsSelected"生成的HTML元素name属性无法被ASP.NET Core模型绑定器识别为Roles集合的一部分,缺少索引标识,导致回传时无法重建列表。 - 缺少RoleName回传:视图中没有提交
RoleName的值,即使IsSelected有值,模型绑定器也无法创建完整的RoleViewModel对象,最终导致整个Roles列表为null。 - 控件类型错误:角色通常是多选场景,使用
radio按钮会导致只能选择一个角色,不符合业务逻辑,同时也会影响表单数据的提交结构。
解决方案
1. 修改视图模型(可选但推荐)
给Roles属性初始化空列表,避免空引用:
public List<RoleViewModel> Roles { get; set; } = new List<RoleViewModel>();
2. 修改视图代码
改用for循环遍历Roles集合(需确保Roles是List<T>类型),同时添加RoleName的隐藏域,并将radio改为checkbox:
<div class="form-group"> <label>Roles</label> @for (int i = 0; i < Model.Roles.Count; i++) { <div class="form-check"> <!-- 隐藏域提交RoleName --> <input type="hidden" asp-for="Roles[i].RoleName" /> <!-- 复选框绑定IsSelected --> <input type="checkbox" asp-for="Roles[i].IsSelected" class="form-check-input" /> <label asp-for="Roles[i].IsSelected" class="form-check-label">@Model.Roles[i].RoleName</label> </div> } </div>
3. 验证模型绑定
修改后,表单提交的参数会以Roles[0].RoleName、Roles[0].IsSelected、Roles[1].RoleName等格式传递,ASP.NET Core模型绑定器能正确识别并重建Roles列表。
额外说明
如果角色数量动态变化,也可以使用EditorTemplate来实现更优雅的集合绑定:
- 在
Views/Shared/EditorTemplates目录下创建RoleViewModel.cshtml文件:
@model FNSD.ViewModels.RoleViewModel <div class="form-check"> <input type="hidden" asp-for="RoleName" /> <input type="checkbox" asp-for="IsSelected" class="form-check-input" /> <label asp-for="IsSelected" class="form-check-label">@Model.RoleName</label> </div>
- 在视图中使用
EditorFor渲染集合:
<div class="form-group"> <label>Roles</label> @Html.EditorFor(m => m.Roles) </div>
内容的提问来源于stack exchange,提问作者user20408154
相关产品推荐
相关产品推荐

