You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
}

问题原因

  1. 集合绑定格式错误:使用@foreach遍历Model.Roles时,asp-for="@role.IsSelected"生成的HTML元素name属性无法被ASP.NET Core模型绑定器识别为Roles集合的一部分,缺少索引标识,导致回传时无法重建列表。
  2. 缺少RoleName回传:视图中没有提交RoleName的值,即使IsSelected有值,模型绑定器也无法创建完整的RoleViewModel对象,最终导致整个Roles列表为null。
  3. 控件类型错误:角色通常是多选场景,使用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来实现更优雅的集合绑定:

  1. 在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>
  1. 在视图中使用EditorFor渲染集合:
<div class="form-group">
    <label>Roles</label>
    @Html.EditorFor(m => m.Roles)
</div>

内容的提问来源于stack exchange,提问作者user20408154

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 04:59:52