接上篇: ASP.NET Core 3.1 实际操作摸索学习 (Identity部分)- 1
小应用是考虑由管理员来导入或手工输入来新建账号,然后分配相应权限来浏览不同页面;
所以考虑做些小的修改调整来适用于一个内部小应用; (比较惭愧,还没来得及研究Razor Page ,只好先用会的二把刀MVC上了 )
新建一个 UserAdmin的控制器,就用空的来写吧:
右键点击打开的 UserAdminController 的
View也用空的来吧:
妖怪的是,怎么没像VS2017 MVC4或MVC5那样,直接在Views目录下建个和控制器相同名字的目录,然后再在里面建好View呢?(目前还没想明白)
为了规范点,还是把 Index.cshtml 手动删除,在Views目录下再建个UserAdmin目录,再建个Index的View:
我把这个UserAdmin放在 Home和Privacy菜单的中间吧,修改_Layout.cshtml
<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">用户管理</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> </li>
首先,先在UserAdmin/Index页面把所有用户列出,并提供编辑、删除按钮: (Index View代码:)
@using Microsoft.AspNetCore.Identity @model IEnumerable<IdentityUser> @{ ViewBag.Title = "Index"; } <div class="panel panel-primary"> <div class="panel-heading"> 用户列表 </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">无用户</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>
UserAdmin控制器代码,先把所有用户传到Index View上显示:
public class UserAdminController : Controller { private readonly UserManager<IdentityUser> _userManager; public UserAdminController(UserManager<IdentityUser> userManager) { _userManager = userManager; } public IActionResult Index() { return View(_userManager.Users); } }
实现结果:
由于应用是一个内部查询应用,密码复杂度要求不需要那么高,调整为长度4位,其他没要求。。。
在Startup.cs 的ConfigureServices 中加入对于密码策略的调整:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("MyConnection")));
services.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = true;
options.Password.RequireDigit = false;
options.Password.RequiredLength = 4;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
}).AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
}
另外,需要把Register.cshtml.cs 中定义的InputModel中的Password的最小长度调整为4:
[Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 4)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; }
用户管理主页面肯定要考虑认证及角色的问题,不可能让未登录用户就可以打开,也不可能让所有登录用户都可以打开,必须是个管理员才可以打开;
那么考虑增加一个角色管理页面来为用户分配角色;
先添加一个RoleAdmin控制器:
再手动在Views目录加一个 RoleAdmin目录,在其中加一个Index的空View:
修改RoleAdmin控制器:
public class RoleAdminController : Controller { private readonly RoleManager<IdentityRole> _roleManager; public RoleAdminController(RoleManager<IdentityRole> roleManager) { _roleManager = roleManager; } public IActionResult Index() { return View(_roleManager.Roles); } }
修改RoleAdmin Index的View:
@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">无角色</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" })
修改 _Layout视图,加上快捷按钮:
<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">用户管理</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>
跑起来看到有角色管理这个按钮了:
但是如果直接点击 角色管理,就会出现以下错误:
初步研究,应该是没有在ConfigureServices没有加这个(应该怎么描述? 依赖注入?)
services.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = true;
options.Password.RequireDigit = false;
options.Password.RequiredLength = 4;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
})
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddEntityFrameworkStores<ApplicationDbContext>();
这下没有问题了,可以打开RoleAdmin页面:
再为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); } }
在RoleAdmin的view目录下,新增一个Create View:
@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" }) }
跑起来以后:
新增2个Role : Admin和NormalUser
下一步考虑为Role来加减下属用户; 本处参考了:https://www.cnblogs.com/r01cn/p/5180892.html#Ch14_3_6 原ASP.NET Identity的一部分代码,学习了其的操作逻辑;
先增加两个ViewModel
public class RoleViewModel { public IdentityRole Role { get; set; } public IEnumerable<IdentityUser> Members { get; set; } public IEnumerable<IdentityUser> NonMembers { get; set; } } public class RoleModificationModel { [Required] public string RoleName { get; set; } public string[] IdsToAdd { get; set; } public string[] IdsToDelete { get; set; } }
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using MyCoreTest1.Models; namespace MyCoreTest1.Controllers { public class RoleAdminController : Controller { private readonly RoleManager<IdentityRole> _roleManager; private readonly UserManager<IdentityUser> _userManager; public RoleAdminController(RoleManager<IdentityRole> roleManager, UserManager<IdentityUser> userManager) { _roleManager = roleManager; _userManager = userManager; } public async Task<IActionResult> Index() { List<RoleViewModel> roleviewmodels = new List<RoleViewModel>(); foreach(IdentityRole role in _roleManager.Roles) { RoleViewModel newrole = new RoleViewModel(); newrole.Role = role; newrole.Members = await _userManager.GetUsersInRoleAsync(role.Name); roleviewmodels.Add(newrole); } return View(roleviewmodels); } 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); } } 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" }); } } }
RoleAdmin改写成以上,首先是把 RoleManager<IdentityRole> roleManager, UserManager<IdentityUser> userManager 传进来;
再把Index的Action 修改一下,可以传一个带用户列表的View Model到前台;
另外,把Edit 的两个Action 按照参考资料里的写法稍微修改一下,来实现Role下属User 的增加删除;
Index的View改成:
@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>下属用户</th><th></th></tr> @if (Model.Count() == 0) { <tr><td colspan="4" class="text-center">无角色</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" })
Role Edit的View改成:
@using MyCoreTest1.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">添加用户至角色:@Model.Role.Name</div> <table class="table table-striped"> @if (Model.NonMembers.Count() == 0) { <tr><td colspan="2">所有用户已添加</td></tr> } else { <tr><td>用户名</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 中移除用户</div> <table class="table table-striped"> @if (Model.Members.Count() == 0) { <tr><td colspan="2">无下属用户</td></tr> } else { <tr><td>用户名</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" }) }
最后效果:
最后把角色管理页面加上 指定的角色才可以打开:
这样,非Admin角色的用户就打不开了: (如果非登录状态,就直接自动跳转登录页面)
只有 Admin角色的用户可以打开 角色管理页面:
下面准备修改 用户管理页面,使得管理员可以 新建、批量导入、重置用户密码等操作;
增加两个User View Model 和一个密码重置用 Model:
public class UserEditViewModel { public IdentityUser 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 = "用户账号")] public string UserName { get; set; } [Required] [StringLength(100, ErrorMessage = "{0}必须长度为:{2} - {1}.", MinimumLength = 4)] [DataType(DataType.Password)] [Display(Name = "新密码")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "确认密码")] [Compare("Password", ErrorMessage = "两次密码输入不一致!")] public string ConfirmPassword { get; set; } }
修改UserAdmin/Index 的 View:
@using Microsoft.AspNetCore.Identity @model IEnumerable<UserEditViewModel> @{ ViewData["Title"] = "用户列表"; } <h1>@ViewData["Title"]</h1> <hr /> <div class="panel panel-primary"> <table class="table table-striped"> <tr><th>账号</th><th>邮箱</th><th>电话</th><th>角色</th><th>操作</th></tr> @if (Model.Count() == 0) { <tr><td colspan="4" class="text-center">无用户</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>
以及对应的Index Action:
public async Task<IActionResult> Index() { List<UserEditViewModel> userViewModels = new List<UserEditViewModel>(); foreach(IdentityUser user in _userManager.Users) { UserEditViewModel newuserviewmodel = new UserEditViewModel(); newuserviewmodel.User = user; newuserviewmodel.Roles = await _userManager.GetRolesAsync(user); userViewModels.Add(newuserviewmodel); } return View(userViewModels); }
效果:
密码重置及编辑对应的Action:
private readonly UserManager<IdentityUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public UserAdminController(UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}
public async Task<ActionResult> ResetPassword(string id) { if (id == null) { return NotFound(); } IdentityUser 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) { IdentityUser user = await _userManager.FindByIdAsync(input.Id); var passwordresettoken = await _userManager.GeneratePasswordResetTokenAsync(user); await _userManager.ResetPasswordAsync(user, passwordresettoken, input.Password); return RedirectToAction("Index"); } return View(); } public async Task<ActionResult> Edit(string id) { if (id == null) { return NotFound(); } IdentityUser 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) { IdentityUser 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" }); }
在UserAdmin View目录下增加 ResetPassword View:
@model ResetPasswordInputModel @{ ViewData["Title"] = "重置用户密码"; } <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");} }
效果:
在UserAdmin View目录下再加一个Edit的View:
@using Microsoft.AspNetCore.Identity @model UserEditViewModel @{ ViewData["Title"] = "编辑用户信息"; } <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">用户角色:</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");} }
效果:
最后把用户管理 加上认证要求: