環(huán)境準(zhǔn)備:
1、VS 2022 免費(fèi)社區(qū)版
本章目標(biāo):
1、增加 用戶(hù) 列表顯示頁(yè)面;
2、增加 角色 維護(hù)主頁(yè)面;
3、增加 編輯 角色下屬 用戶(hù) 界面;
4、修改用戶(hù)管理界面,可以編輯用戶(hù)所屬角色、重置密碼等操作;
5、開(kāi)啟最基本的 角色 認(rèn)證;
1、增加 用戶(hù)列表 顯示頁(yè)面
新建一個(gè) UserAdmin的控制器:

就選擇 MVC 控制器-空:

名稱(chēng): UserAdminController

打開(kāi)UserAdminController.cs后,在默認(rèn)的 Index 右鍵選擇 添加視圖:

簡(jiǎn)單一點(diǎn),就用空視圖:

名字不用改,就Index.cshtml

會(huì)自動(dòng)在 Views目錄下,增加 UserAdmin 目錄,并增加空的 Index.cshtml ,并自動(dòng)打開(kāi):

在Index.cshtml 視圖中插入 顯示用戶(hù)列表的 View,代碼如下:
@using Microsoft.AspNetCore.Identity @model IEnumerable<IdentityUser> @{ ViewBag.Title = "Index"; } <div class="panel panel-primary"> <div class="panel-heading"> 用戶(hù)列表 </div> <table class="table table-striped"> <tr><th>Name</th><th>Email</th><th>PhoneNumber</th><th></th></tr> @if (Model.Count() == 0) { <tr><td colspan="4" class="text-center">無(wú)用戶(hù)</td></tr> } else { foreach (IdentityUser user in Model) { <tr> <td>@user.UserName</td> <td>@user.Email</td> <td>@user.PhoneNumber</td> <td> @Html.ActionLink("密碼重置", "ResetPassword", new { id = user.Id }, new { @class = "btn btn-warning btn-xs" }) @Html.ActionLink("編輯", "Edit", new { id = user.Id }, new { @class = "btn btn-primary btn-xs" }) @Html.ActionLink("刪除", "Delete", new { id = user.Id }, new { @class = "btn btn-danger btn-xs" }) </td> </tr> } } </table> </div>
修改 UserAdminController ,先把 UserManager 傳入,再修改 Index View的返回為所有 User:
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; namespace POSystem.Controllers { public class UserAdminController : Controller { private readonly UserManager<IdentityUser> _userManager; public UserAdminController(UserManager<IdentityUser> userManager) { _userManager = userManager; } public IActionResult Index() { return View(_userManager.Users); } } }
先把這個(gè)UserAdmin的按鈕入口 放在 Home和Privacy菜單的中間,修改_Layout.cshtml
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> <ul class="navbar-nav flex-grow-1"> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="UserAdmin" asp-action="Index">用戶(hù)管理</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> </li> </ul> <partial name="_LoginPartial" /> </div>
列表效果:

注:密碼重置、編輯、刪除 待補(bǔ)充。
2、增加 角色 維護(hù)主頁(yè)面;
增加一個(gè)角色維護(hù)管理頁(yè)面來(lái)為用戶(hù)分配角色;先添加一個(gè)RoleAdminController 控制器:

同樣再加個(gè)Index View:

修改 RoleAdminController, 傳入 RoleManager, Index返回所有Role給View:
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; namespace POSystem.Controllers { public class RoleAdminController : Controller { private readonly RoleManager<IdentityRole> _roleManager; public RoleAdminController(RoleManager<IdentityRole> roleManager) { _roleManager = roleManager; } public IActionResult Index() { return View(_roleManager.Roles); } } }
修改Index View,先最簡(jiǎn)單的顯示一個(gè)列表:
@using Microsoft.AspNetCore.Identity @model IEnumerable<IdentityRole> @{ ViewBag.Title = "Index"; } <div class="panel panel-primary"> <div class="panel-heading"> 角色列表 </div> <table class="table table-striped"> <tr><th>Name</th><th></th></tr> @if (Model.Count() == 0) { <tr><td colspan="4" class="text-center">無(wú)角色</td></tr> } else { foreach (IdentityRole role in Model) { <tr> <td>@role.Name</td> <td> @Html.ActionLink("編輯", "Edit", new { id = role.Id }, new { @class = "btn btn-primary btn-xs" }) @Html.ActionLink("刪除", "Delete", new { id = role.Id }, new { @class = "btn btn-danger btn-xs" }) </td> </tr> } } </table> </div>@Html.ActionLink("新建", "Create","RoleAdmin",null, new { @class = "btn btn-primary btn-xs" })
繼續(xù)修改 _Layout.cshtml :
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> <ul class="navbar-nav flex-grow-1"> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="UserAdmin" asp-action="Index">用戶(hù)管理</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="RoleAdmin" asp-action="Index">角色管理</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> </li> </ul> <partial name="_LoginPartial" /> </div>
跑起來(lái)看到有角色管理這個(gè)按鈕了:

但是一點(diǎn)就出錯(cuò):

初步研究,應(yīng)該是沒(méi)有加這個(gè):
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddEntityFrameworkStores<ApplicationDbContext>();
加完,能正常顯示RoleAdmin的Index:

在RoleAdmin 控制器中增加 Create Action :
public ActionResult Create() { return View(); } [HttpPost] public async Task<ActionResult> Create([Required] string name) { if (ModelState.IsValid) { IdentityResult result = await _roleManager.CreateAsync(new IdentityRole(name)); if (result.Succeeded) { return RedirectToAction("Index"); } else { AddErrorsFromResult(result); } } return View(); } private void AddErrorsFromResult(IdentityResult result) { foreach (IdentityError error in result.Errors) { ModelState.AddModelError("", error.Description); } }
再為Create Action 添加一個(gè)空視圖,并把以下視圖代碼加入:
@model string @{ ViewBag.Title = "新增角色";} <h2>新增角色</h2> @Html.ValidationSummary(false) @using (Html.BeginForm()) { <div class="form-group"> <label>Name</label> <input name="name" value="@Model" class="form-control" /> </div> <button type="submit" class="btn btn-primary">新增</button> @Html.ActionLink("取消", "Index", "RoleAdmin", null, new { @class = "btn btn-default btn-xs" }) }
運(yùn)行后,點(diǎn)擊 新建 按鈕:

建一個(gè) Admin 、ReadOnlyUser 、OperationUser :

3、增加 編輯 角色下屬 用戶(hù) 界面;
考慮在角色界面,顯示每個(gè)角色對(duì)應(yīng)的用戶(hù);并增加角色維護(hù)用戶(hù)頁(yè)面(即 編輯 頁(yè)面)
先增加兩個(gè)ViewModel:
using Microsoft.AspNetCore.Identity; namespace POSystem.Models { public class RoleViewModel { public IdentityRole Role { get; set; } public IEnumerable<IdentityUser> Members { get; set; } public IEnumerable<IdentityUser> NonMembers { get; set; } } }
using System.ComponentModel.DataAnnotations; namespace POSystem.Models { public class RoleModificationModel { [Required] public string RoleName { get; set; } public string[]? IdsToAdd { get; set; } public string[]? IdsToDelete { get; set; } } }
private readonly RoleManager<IdentityRole> _roleManager; private readonly UserManager<IdentityUser> _userManager; public RoleAdminController(RoleManager<IdentityRole> roleManager, UserManager<IdentityUser> userManager) { _roleManager = roleManager; _userManager = userManager; }
RoleAdmin改寫(xiě)成以上,首先是把 RoleManager<IdentityRole> roleManager, UserManager<IdentityUser> userManager 傳進(jìn)來(lái);
增加 Edit 兩個(gè)Action, 一個(gè)HTTP GET 一個(gè)對(duì)應(yīng)HTTP POST:
public async Task<ActionResult> Edit(string id) { IdentityRole role = await _roleManager.FindByIdAsync(id); IEnumerable<IdentityUser> members = await _userManager.GetUsersInRoleAsync(role.Name); List<IdentityUser> nonMembers = new List<IdentityUser>(); foreach (IdentityUser user in _userManager.Users) { if (!members.Any(x => x.Id.Equals(user.Id))) { nonMembers.Add(user); } } return View(new RoleViewModel { Role = role, Members = members, NonMembers = nonMembers }); } [HttpPost] public async Task<ActionResult> Edit(RoleModificationModel model) { IdentityResult result; if (ModelState.IsValid) { foreach (string userId in model.IdsToAdd ?? new string[] { }) { var user = await _userManager.FindByIdAsync(userId); result = await _userManager.AddToRoleAsync(user, model.RoleName); if (!result.Succeeded) { return View("Error", result.Errors); } } foreach (string userId in model.IdsToDelete ?? new string[] { }) { var user = await _userManager.FindByIdAsync(userId); result = await _userManager.RemoveFromRoleAsync(user, model.RoleName); if (!result.Succeeded) { return View("Error", result.Errors); } } return RedirectToAction("Index"); } return View("Error", new string[] { "Role Not Found" }); }
增加Edit的視圖,用空視圖,然后用以下代碼代替:
@using POSystem.Models @using Microsoft.AspNetCore.Identity @model RoleViewModel @{ ViewBag.Title = "Edit Role";} @Html.ValidationSummary() @using (Html.BeginForm()) { <input type="hidden" name="roleName" value="@Model.Role.Name" /> <div class="panel panel-primary"> <div class="panel-heading">添加用戶(hù)至角色:@Model.Role.Name</div> <table class="table table-striped"> @if (Model.NonMembers.Count() == 0) { <tr><td colspan="2">所有用戶(hù)已添加</td></tr> } else { <tr><td>用戶(hù)名</td><td>添加至角色</td></tr> foreach (IdentityUser user in Model.NonMembers) { <tr> <td>@user.UserName</td> <td> <input type="checkbox" name="IdsToAdd" value="@user.Id"> </td> </tr> } } </table> </div> <div class="panel panel-primary"> <div class="panel-heading">從角色:@Model.Role.Name 中移除用戶(hù)</div> <table class="table table-striped"> @if (Model.Members.Count() == 0) { <tr><td colspan="2">無(wú)下屬用戶(hù)</td></tr> } else { <tr><td>用戶(hù)名</td><td>從角色中移除</td></tr> foreach (IdentityUser user in Model.Members) { <tr> <td>@user.UserName</td> <td> <input type="checkbox" name="IdsToDelete" value="@user.Id"> </td> </tr> } } </table> </div> <button type="submit" class="btn btn-primary">保存</button> @Html.ActionLink("取消", "Index", null, null, new { @class = "btn btn-default" }) }
點(diǎn)擊 ReadOnlyUser 角色的編輯按鈕,可以勾選用戶(hù)添加至角色:

上一步點(diǎn)保存后,再點(diǎn)擊 ReadOnlyUser 角色的編輯按鈕,可以看到 已經(jīng)在角色列表,并可以勾選移除:


為了直觀的顯示每個(gè)角色下面有哪些用戶(hù),則考慮修改Index Action和視圖:
public async Task<IActionResult> Index() { List<RoleViewModel> roleviewmodels = new List<RoleViewModel>(); foreach (IdentityRole role in _roleManager.Roles.ToList()) { RoleViewModel newrole = new RoleViewModel(); newrole.Role = role; newrole.Members = await _userManager.GetUsersInRoleAsync(role.Name); roleviewmodels.Add(newrole); } return View(roleviewmodels); }
此處有個(gè) MySQL 和 MSSQL不同的地方,如果紅色的 .ToList() 不加,則會(huì)報(bào)錯(cuò),提示:
InvalidOperationException: This MySqlConnection is already in use. See https://fl.vu/mysql-conn-reuse
最后經(jīng)過(guò)查詢(xún),知道MySQL不支持一個(gè)特性:MultipleActiveResultSets
修改Index 視圖如下:
@using Microsoft.AspNetCore.Identity @model IEnumerable<RoleViewModel> @{ ViewBag.Title = "Index"; } <div class="panel panel-primary"> <div class="panel-heading"> 角色列表 </div> <table class="table table-striped"> <tr><th>角色名</th><th>下屬用戶(hù)</th><th></th></tr> @if (Model.Count() == 0) { <tr><td colspan="4" class="text-center">無(wú)角色</td></tr> } else { foreach (RoleViewModel roleview in Model) { <tr> <td>@roleview.Role.Name</td> <td> @foreach (IdentityUser user in @roleview.Members) { @user.UserName@Html.Label(";") } </td> <td>@Html.ActionLink("編輯", "Edit", new { id = roleview.Role.Id },new { @class = "btn btn-primary btn-xs" }) @Html.ActionLink("刪除", "Delete", new { id = roleview.Role.Id },new { @class = "btn btn-danger btn-xs" }) </td> </tr> } } </table> </div> @Html.ActionLink("新建", "Create","RoleAdmin",null, new { @class = "btn btn-primary btn-xs" })
最后體現(xiàn)結(jié)果:

4、修改用戶(hù)管理界面,可以編輯用戶(hù)所屬角色、重置密碼等操作;
增加兩個(gè)User View Model 和一個(gè)密碼重置用 Model:
public class UserEditViewModel { public POSystemUser User { get; set; } public IEnumerable<string> Roles { get; set; } public IEnumerable<IdentityRole> AllRoles { get; set; } } public class UserModificationModel { [Required] public string Id { get; set; } public string UserName { get; set; } public string Email { get; set; } public string PhoneNumber { get; set; } public string[] RolesToAdd { get; set; } } public class ResetPasswordInputModel { public string Id { get; set; } [Display(Name = "用戶(hù)賬號(hào)")] public string UserName { get; set; } [Required] [StringLength(100, ErrorMessage = "{0}必須長(zhǎng)度為:{2} - {1}.", MinimumLength = 4)] [DataType(DataType.Password)] [Display(Name = "新密碼")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "確認(rèn)密碼")] [Compare("Password", ErrorMessage = "兩次密碼輸入不一致!")] public string ConfirmPassword { get; set; } }
修改 UserAdminController 的Index: (注意紅色部分,不加.ToList() , MySQL 數(shù)據(jù)庫(kù)會(huì)出錯(cuò))
public async Task<IActionResult> Index() { List<UserEditViewModel> userViewModels = new List<UserEditViewModel>(); foreach (POSystemUser user in _userManager.Users.ToList()) { UserEditViewModel newuserviewmodel = new UserEditViewModel(); newuserviewmodel.User = user; newuserviewmodel.Roles = await _userManager.GetRolesAsync(user); userViewModels.Add(newuserviewmodel); } return View(userViewModels); }
修改 Index View:
@using Microsoft.AspNetCore.Identity @model IEnumerable<UserEditViewModel> @{ ViewData["Title"] = "用戶(hù)列表"; } <h1>@ViewData["Title"]</h1> <hr /> <div class="panel panel-primary"> <table class="table table-striped"> <tr><th>賬號(hào)</th><th>郵箱</th><th>電話</th><th>角色</th><th>操作</th></tr> @if (Model.Count() == 0) { <tr><td colspan="4" class="text-center">無(wú)用戶(hù)</td></tr> } else { foreach (UserEditViewModel userview in Model) { <tr> <td>@userview.User.UserName</td> <td>@userview.User.Email</td> <td>@userview.User.PhoneNumber</td> <td> @foreach (string rolename in @userview.Roles) { @Html.Label(rolename + ";") } </td> <td> @Html.ActionLink("密碼重置", "ResetPassword", new { id = userview.User.Id }, new { @class = "btn btn-warning btn-xs" }) @Html.ActionLink("編輯", "Edit", new { id = userview.User.Id }, new { @class = "btn btn-primary btn-xs" }) @Html.ActionLink("刪除", "Delete", new { id = userview.User.Id }, new { @class = "btn btn-danger btn-xs" }) </td> </tr> } } </table> </div>
增加 roleManager的注入,再加入 Edit 和 ResetPassword 兩個(gè)Action對(duì)應(yīng)的 Get 和 Post
private readonly UserManager<POSystemUser> _userManager; private readonly RoleManager<IdentityRole> _roleManager; public UserAdminController(UserManager<POSystemUser> userManager, RoleManager<IdentityRole> roleManager) { _userManager = userManager; _roleManager = roleManager; } public async Task<ActionResult> Edit(string id) { if (id == null) { return NotFound(); } POSystemUser user = await _userManager.FindByIdAsync(id); if (user == null) { return NotFound(); } UserEditViewModel edituser = new UserEditViewModel(); edituser.User = user; edituser.Roles = await _userManager.GetRolesAsync(user); edituser.AllRoles = _roleManager.Roles; return View(edituser); } [HttpPost] public async Task<ActionResult> Edit(UserModificationModel model) { if (ModelState.IsValid) { POSystemUser user = await _userManager.FindByIdAsync(model.Id); if (user == null) { return View("Error", new string[] { "User Not Found" }); } user.Email = model.Email; user.PhoneNumber = model.PhoneNumber; var rolesselect = model.RolesToAdd ?? new string[] { }; foreach (string role in rolesselect) { if (await _userManager.IsInRoleAsync(user, role)) { } else { await _userManager.AddToRoleAsync(user, role); } } //還需要去除被反勾選的Role var userroles = await _userManager.GetRolesAsync(user); foreach (string role in userroles) { if (!rolesselect.Contains(role)) { await _userManager.RemoveFromRoleAsync(user, role); } } await _userManager.UpdateAsync(user); return RedirectToAction("Index"); } return View("Error", new string[] { "User Not Found" }); } public async Task<ActionResult> ResetPassword(string id) { if (id == null) { return NotFound(); } POSystemUser user = await _userManager.FindByIdAsync(id); if (user == null) { return NotFound(); } ResetPasswordInputModel resetuser = new ResetPasswordInputModel(); resetuser.Id = user.Id; resetuser.UserName = user.UserName; return View(resetuser); } [HttpPost] public async Task<ActionResult> ResetPassword(ResetPasswordInputModel input) { if (ModelState.IsValid) { POSystemUser user = await _userManager.FindByIdAsync(input.Id); var passwordresettoken = await _userManager.GeneratePasswordResetTokenAsync(user); await _userManager.ResetPasswordAsync(user, passwordresettoken, input.Password); return RedirectToAction("Index"); } return View(); }
增加 ResetPassword View:
@model ResetPasswordInputModel @{ ViewData["Title"] = "重置用戶(hù)密碼"; } <h1>@ViewData["Title"]</h1> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="ResetPassword"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <input type="hidden" asp-for="Id" /> <div class="form-group"> <label asp-for="UserName" class="control-label"></label> <input asp-for="UserName" class="form-control" readonly="readonly" /> <span asp-validation-for="UserName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Password" class="control-label"></label> <input asp-for="Password" class="form-control" /> <span asp-validation-for="Password" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="ConfirmPassword" class="control-label"></label> <input asp-for="ConfirmPassword" class="form-control" /> <span asp-validation-for="ConfirmPassword" class="text-danger"></span> </div> <div class="form-group"> <input type="submit" value="Save" class="btn btn-primary" /> </div> </form> </div> </div> <div> <a asp-action="Index">返回目錄</a> </div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
再加一個(gè)Edit的View:
@using Microsoft.AspNetCore.Identity @model UserEditViewModel @{ ViewData["Title"] = "編輯用戶(hù)信息"; } <h1>@ViewData["Title"]</h1> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="Edit"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <input type="hidden" asp-for="User.Id" /> <div class="form-group"> <label asp-for="User.UserName" class="control-label"></label> <input asp-for="User.UserName" class="form-control" readonly="readonly" /> <span asp-validation-for="User.UserName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="User.Email" class="control-label"></label> <input asp-for="User.Email" name="Email" class="form-control" /> <span asp-validation-for="User.Email" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="User.PhoneNumber" class="control-label"></label> <input asp-for="User.PhoneNumber" name="PhoneNumber" class="form-control" /> <span asp-validation-for="User.PhoneNumber" class="text-danger"></span> </div> <div class="form-group"> <label class="control-label">用戶(hù)角色:</label> @foreach (IdentityRole role in Model.AllRoles) { <tr> <td>@role.Name</td> <td> @if (@Model.Roles.Contains(role.Name)) { <input type="checkbox" name="RolesToAdd" value="@role.Name" checked="checked" /> } else { <input type="checkbox" name="RolesToAdd" value="@role.Name" /> } </td> </tr> } </div> <div class="form-group"> <input type="submit" value="Save" class="btn btn-primary" /> </div> </form> </div> </div> <div> <a asp-action="Index">返回目錄</a> </div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
5、開(kāi)啟最簡(jiǎn)單直接的角色認(rèn)證;
最后把 Role管理頁(yè)面 和 User管理頁(yè)面 設(shè)置成只有Admin 角色才可以操作;
在控制器 加上 [Authorize(Roles = "Admin")] 即可實(shí)現(xiàn):
[Authorize(Roles = "Admin")] public class RoleAdminController : Controller
浙公網(wǎng)安備 33010602011771號(hào)