入門實戰-權限管理之角色編輯和賦權(ViewModel-DTO初探)
前面幾章講了選單功能的管理之后,我們再創建一個角色管理的功能,創建程序不再詳細介紹,只要按照選單管理功能的步驟進行創建即可;和前面講的稍微不同的是,現在多了一個Service層和異步操作,功能處理的代碼放在這個層完成,只在Controller中呼叫即可,
一、再講角色的操作之前,我們先聊一個概念:ViewModel,或者叫DTO;兩個是一個意思,叫法不一樣而已,ViewModel是ASP.NET MVC應用中的隱式宣告的層,用來維護Model和View之間的資料傳遞, 因此,ViewModel也簡稱為資料傳輸物件或 DTO,
我們舉個簡單的例子:我先以原來Menu選單功能中的一個查看選單明細Details來舉例說明,
(1):其View代碼(Details.cshtml)如下

(2):其Controller的Details Action代碼如下:

(3):其預覽效果為:

Controller傳遞到View視圖的資料是item,并還定了一個ViewBag.PageTitle的資料,我想把ViewBag.PageTitle個跟隨Model item一并傳遞到View,而且不用ViewBag的方式,那么我就新建一個ViewModel型別的Model,叫MenuView,它的代碼是這樣:


在Controller中改如何獲取值:

在View中如何顯示值:

以上就是一個ViewModel的簡單應用例子,很好理解,
二、在接著說角色功能的例子,簡單說下程序
1.創建角色資料表ManagerRole;
CREATE TABLE [dbo].[ManagerRole]( [Id] [int] IDENTITY(1,1) NOT NULL, [RoleName] [varchar](64) NOT NULL, [RoleType] [int] NOT NULL, [IsSystem] [bit] NOT NULL, [AddManagerId] [int] NOT NULL, [AddTime] [datetime] NOT NULL, [ModifyManagerId] [int] NULL, [ModifyTime] [datetime] NULL, [IsDelete] [bit] NOT NULL, [Remark] [varchar](128) NULL, CONSTRAINT [PK_MANAGERROLE] PRIMARY KEY NONCLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
2.撰寫角色Model,ManagerRole;
public class ManagerRole { /// <summary> /// 主鍵 /// </summary> [Key] public int Id { get; set; } /// <summary> /// 角色名稱 /// </summary> [Required] public String RoleName { get; set; } /// <summary> /// 角色型別1超管2系管 /// </summary> [Required] public int RoleType { get; set; } /// <summary> /// 是否系統默認 /// </summary> [Required] public Boolean IsSystem { get; set; } /// <summary> /// 添加人 /// </summary> public int AddManagerId { get; set; } /// <summary> /// 添加時間 /// </summary> public DateTime AddTime { get; set; } /// <summary> /// 修改人 /// </summary> public int? ModifyManagerId { get; set; } /// <summary> /// 修改時間 /// </summary> public DateTime? ModifyTime { get; set; } /// <summary> /// 是否洗掉 /// </summary> public Boolean IsDelete { get; set; } /// <summary> /// 備注 /// </summary> public String Remark { get; set; }
3.撰寫角色Controller[ManagerRoleController.cs]和View;

(3.1)Index.cshtml的View視圖代碼
@using RjWebCms.Db; @model PaginatedList<ManagerRole> @{ ViewData["Title"] = "角色串列"; } @section Scripts{ <script type="text/javascript"> $(document).ready(function () { $("a[name='DelAll']").click(function (){ DelAll(); }); }); function DelAll() { var ids = document.getElementsByName("chk_ids"); var arrIds = ""; var n = 0; for (var i = 0; i < ids.length; i++) { if (ids[i].checked == true) { arrIds += ids[i].value + ","; n++; } } if (n == 0) { alert("請選擇要洗掉的資訊"); return; } arrIds = arrIds.substr(0, arrIds.length - 1); $.ajax({ type: "POST", url: "/ManagerRole/DeleteAll", data: { idString: arrIds }, beforeSend: function (xhr) { xhr.setRequestHeader("RequestVerificationToken", $('input:hidden[name="__RequestVerificationToken"]').val()); }, success: function (res) { alert('洗掉成功!'); window.location.href = "/ManagerRole/Index"; }, failure: function (data, error) { alert('洗掉失敗'); } }); } function del() { $.ajax({ type: "POST", url: "/ManagerRole/Details", data: {id:8}, beforeSend: function (xhr) { xhr.setRequestHeader("RequestVerificationToken", $('input:hidden[name="__RequestVerificationToken"]').val()); }, success: function (response) { alert('OK'); alert(response); }, failure: function (response) { alert(response); } }); } </script> } <div class="panel panel-default todo-panel"> <div class="panel-heading">@ViewData["Title"]</div> @Html.AntiForgeryToken() <form asp-action="Index" method="get"> <table> <tr><td><a asp-controller="ManagerRole" asp-action="Create">添加</a></td></tr> <tr> <td>查詢關鍵詞:<input type="text" name="SearchString" value="@ViewData["CurrentFilter"]" /></td> <td><input type="submit" value="查詢" /></td> <td><a asp-action="Index">Back</a></td> <td><a id="DelAll" name="DelAll">批量洗掉</a></td> </tr> </table> </form> <table class="table table-hover"> <thead> <tr> <td>✔</td> <td><a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">角色名稱</a></td> <td>角色型別</td> <td><a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">時間</a></td> <td>操作</td> </tr> @foreach (var item in Model) { <tr> <td><input type="checkbox" class="done-checkbox" name="chk_ids" value="@item.Id"></td> <td>@item.RoleName</td> <td>@item.RoleType</td> <td>@item.AddTime</td> <td> <a asp-controller="ManagerRole" asp-action="Details" asp-route-id="@item.Id">View</a> <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> <a asp-controller="RolePermission" asp-action="Index" asp-route-id="@item.Id">Menu</a> <a asp-controller="ManagerRole" asp-action="Delete" asp-route-id="@item.Id">Delete</a> </td> </tr> } </thead> </table> @{ var prevDisabled = !Model.HasPreviousPage ? "disabled" : ""; var nextDisabled = !Model.HasNextPage ? "disabled" : ""; ; } <a asp-action="Index" asp-route-sortOrder="@ViewData["CurrentSort"]" asp-route-pageNumber="@(Model.PageIndex - 1)" asp-route-currentFilter="@ViewData["CurrentFilter"]" class="btn btn-default @prevDisabled"> 上一頁 </a> <a asp-action="Index" asp-route-sortOrder="@ViewData["CurrentSort"]" asp-route-pageNumber="@(Model.PageIndex + 1)" asp-route-currentFilter="@ViewData["CurrentFilter"]" class="btn btn-default @nextDisabled"> 下一頁 </a> <div class="panel-footer add-item-form"> <!-- TODO: Add item form --> </div> </div>
(3.2)Create.cshtml視圖代碼
@{ ViewData["Title"] = "新建角色"; } @model ManagerRole <form action="/ManagerRole/Create" method="post"> @Html.AntiForgeryToken() <div> <label asp-for="RoleName">角色名稱</label> <div> <input type="text" asp-for="RoleName" name="RoleName" placeholder="請輸入角色名稱"> </div> </div> <div> <label asp-for="RoleType">角色型別</label> <div> <select name="RoleType" class="RoleType"> <option value="1">超級管理員</option> <option value="2" selected>系統管理員</option> </select> </div> </div> <div> <label asp-for="IsSystem">系統默認</label> <div> <select asp-for="IsSystem" name="IsSystem" class="IsSystem"> <option value="False" selected>否</option> <option value="True">是</option> </select> </div> </div> <div> <label asp-for="Remark">備注</label> <div> <textarea placeholder="請輸入備注資訊" asp-for="Remark" name="Remark"></textarea> </div> </div> <div> <div> <button type="submit">確定</button> <button type="reset">重置</button> </div> </div> </form>
(3.3)Edit.cshtml視圖代碼
@{ ViewData["Title"] = "角色編輯"; } @model ManagerRole <form action="/ManagerRole/Edit" method="post"> @Html.AntiForgeryToken() <input type="hidden" asp-for="Id" /> <div> <label asp-for="RoleName">角色名稱</label> <div> <input type="text" asp-for="RoleName" name="RoleName" class="layui-input RoleName" lay-verify="required" placeholder="請輸入角色名稱"> </div> </div> <div> <label asp-for="RoleType">角色型別</label> <div> <select name="RoleType" class="RoleType"> <option value="1" selected="@(Model.RoleType == 1 ? " selected" : null)">超級管理員</option> <option value="2" selected="@(Model.RoleType == 2 ? " selected" : null)">系統管理員</option> </select> </div> </div> <div> <label asp-for="IsSystem">系統默認</label> <div> <select asp-for="IsSystem" name="IsSystem" class="IsSystem"> <option value="false" selected="@(Model.IsSystem==false ? " selected" : null)">否</option> <option value="true" selected="@(Model.IsSystem==true ? " selected" : null)">是</option> </select> </div> </div> <div> <label asp-for="Remark">備注</label> <div> <textarea placeholder="請輸入備注資訊" asp-for="Remark" name="Remark" class="layui-textarea Remark"></textarea> </div> </div> <div> <div> <button type="submit">確定</button> <button type="reset">重置</button> </div> </div> </form>
(3.4)ManagerController.cs控制器代碼如下,注意各個Action的型別宣告,在Index這個Action上,我加了個[Authorize]宣告,意思是,如果你要查看角色,必須是登錄用戶才行,
public class ManagerRoleController : Controller { private readonly IManagerRoleService _roleService; private readonly AppDbContext _appDbContext; public ManagerRoleController(IManagerRoleService roleService, AppDbContext appDbContext) { _roleService = roleService; _appDbContext = appDbContext; } [Authorize] public async Task<IActionResult> Index(string sortOrder,string currentFilter,string searchString,int? pageNumber) { ViewData["CurrentSort"] = sortOrder; ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : ""; ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date"; if (searchString != null) { pageNumber = 1; } else { searchString = currentFilter; } ViewData["CurrentFilter"] = searchString; var mRoles = from s in _appDbContext.ManagerRole select s; if (!string.IsNullOrEmpty(searchString)) { mRoles = mRoles.Where(s => s.RoleName.Contains(searchString)); } switch (sortOrder) { case "name_desc": mRoles = mRoles.OrderByDescending(s => s.RoleName); break; case "Date": mRoles = mRoles.OrderBy(s => s.AddTime); break; case "date_desc": mRoles = mRoles.OrderByDescending(s => s.AddTime); break; default: mRoles = mRoles.OrderBy(s => s.RoleName); break; } int pageSize = 4; return View(await PaginatedList<ManagerRole>.CreateAsync(mRoles.AsNoTracking(), pageNumber ?? 1, pageSize)); } [HttpGet] public IActionResult Create() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create(ManagerRole mRole) { //去掉對欄位IsSystem的驗證,IsSystem在資料庫是bool型別,而前端是0和1,ModelState的驗證總是報false,所以去掉對其驗證 //ModelState.Remove("IsSystem");//在View端已經解決了了bool型別,那么此行代碼可以不用 if (ModelState.IsValid) { var successful = await _roleService.AddManagerRoleAysnc(mRole); if (successful) return RedirectToAction("RoleList"); else return BadRequest("失敗"); } return View(mRole); } [HttpGet] public async Task<IActionResult> Edit(int id) { if (string.IsNullOrEmpty(id.ToString())) return NotFound(); var mRole = await _roleService.FindManagerRoleAsync(id); if (mRole == null) return NotFound(); return View(mRole); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Edit(int id,ManagerRole mRole) { if (id != mRole.Id) { return NotFound(); } if (ModelState.IsValid) { try { var result = await _roleService.UpdateManagerRoleAsync(id,mRole); } catch (Exception ex) { return BadRequest("編輯失敗"); } return RedirectToAction("Index"); } return View(mRole); } [HttpGet] public async Task<IActionResult> Delete(int id) { var result = await _roleService.DeleteManagerRoleAsync(id); if (result) return RedirectToAction("Index"); else return BadRequest("洗掉失敗"); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> DeleteAll(string idString) { int countSuccessDel = 0;//記錄洗掉成功的資料條數 int countFailedDel = 0;//記錄洗掉成功的資料條數 if (idString.Length > 0) { if (idString.IndexOf(',') > 0) { string[] ids = idString.Split(','); foreach (string id in ids) { var result = await _roleService.DeleteManagerRoleAsync(int.Parse(id)); if(result) countSuccessDel++; else countFailedDel++; } if (countSuccessDel > 0) return RedirectToAction("Index"); else return BadRequest("洗掉失敗"); } } return BadRequest("失敗"); } [HttpGet] public async Task<IActionResult> Details(int id) { if (string.IsNullOrEmpty(id.ToString())) return NotFound(); var mRole = await _roleService.FindManagerRoleAsync(id); if (mRole == null) return NotFound(); return View(mRole); } }
(3.5)給角色賦權選單
在串列頁,也就是Index的Action內對其進行分頁和添加相關的增、刪、改、查的按鈕,串列頁多一個設定選單的按鈕(Menu),點擊Menu按鈕的目的是為了給這個角色賦予選單的權限,這是權限管理控制到選單級別的一種處理方式,

我在選單串列頁(Index.cshtml)中Menu的鏈接我是這樣寫的如下圖:

鏈接到新的Controller和對應的Action:RolePermission是表[RolePermission]對應的Controller,它存盤了角色Id與選單Id的關系;

一個角色如何設定和編輯其具有哪些選單的頁面視圖如下:

(3.6)上面設定角色選單的這個頁面的View代碼如下(從這里開始那么涉及到了ViewModel的功能),可以看@model指令,我引入了一個MenuView,這是一個ViewModel型別,因為需要集合資料,單純的一個Menu無法滿足我的資料顯示和操作,看完View代碼,請注意對應的Controller代碼:

@model MenuView @{ ViewData["Title"] = "設定角色的權限選單"; } @section Scripts{ <script type="text/javascript" src="~/js/jquery-3.6.1.min.js"></script> <script type="text/javascript"> $(document).ready(function () { $("#btnSave").click(function () { SetCheckBoxSelected(); }); }); function SetCheckBoxSelected() { var ids = document.getElementsByName("Cbox_H"); var arrIds = ""; var n = 0; for (var i = 0; i < ids.length; i++) { if (ids[i].checked == true) { arrIds += ids[i].value + ","; n++; } } if (n == 0) { alert("請選擇要設定的權限選單"); return; } arrIds = arrIds.substr(0, arrIds.length - 1);//選擇的選單id集合 var roleId = document.getElementById("hidden_roleid").value; //角色id $.ajax({ type: "POST", url: "/RolePermission/Edit", data: { idString: arrIds, roleid: roleId }, beforeSend: function (xhr) { xhr.setRequestHeader("RequestVerificationToken", $('input:hidden[name="__RequestVerificationToken"]').val()); }, success: function (res) { alert('設定成功!'); //window.location.href = "https://www.cnblogs.com/RolePermission/Index"; }, failure: function (data, error) { alert('洗掉失敗'); } }); } </script> } <div class="panel panel-default todo-panel"> <div class="panel-heading">@ViewData["Title"]</div> @Html.AntiForgeryToken() <div> 當前角色:<label id="lab_rolename">@ViewData["roleName"]</label> <button type="button" id="btnSave">保存</button> <input type="hidden" id="hidden_roleid" value="@ViewData["roleId"]"/> </div> <table class="table table-hover"> <thead> <tr> <td>系統類別</td> <td>選單名稱</td> </tr> @foreach (var item in Model.MenuList) { if (item.ParentId == 0) { <tr> <td> <input type="checkbox" name="Cbox_H" value="@item.Id" @(Html.Raw(Model.MenuIds.Contains(item.Id)? "checked=\"checked\"" : "")) /> @item.DisplayName </td> <td> </td> </tr> } else { <tr> <td> </td> <td> <input type="checkbox" name="Cbox_H" value="@item.Id" @(Html.Raw(Model.MenuIds.Contains(item.Id) ? "checked=\"checked\"" : "")) /> @item.DisplayName </td> </tr> } } </thead> </table> <div class="panel-footer add-item-form"> <!-- TODO: Add item form --> </div> </div>
這里著重理解一下代碼:<input type="checkbox" name="Cbox_H" value="https://www.cnblogs.com/mushaobai/p/@item.Id"
@(Html.Raw(Model.MenuIds.Contains(item.Id) ? "checked=\"checked\"" : "")) />
就是復選框賦值問題:通過三元運算,判斷當前RoleId下有這個MenuId,則選中復選框,沒有這個MenuId,就置空,
簡單說下處理邏輯:
(1).視圖頁面將列出所有選單名稱(帶復選框);點開本頁就只有一種操作-編輯;
(2).編輯時,需要先將原有選擇保存過的顯示勾選上,然后可再勾選編輯保存;
(3.7)權限勾選選單設定的Controller操作代碼,
3.7.1 先看MenuView代碼

3.7.2再看RolePermissionController.cs的代碼
public class RolePermissionController : Controller { private readonly IRolePermissionService _rolePermissionService; private readonly IManagerRoleService _managerRoleService; private readonly IMenuService _menuService; private readonly AppDbContext _appDbContext; public RolePermissionController(IRolePermissionService rolePermissionService, IManagerRoleService managerRoleService,IMenuService menService,AppDbContext appDbContext) { _rolePermissionService = rolePermissionService; _managerRoleService = managerRoleService; _menuService = menService; _appDbContext = appDbContext; } public async Task<IActionResult> IndexAsync(int id) { ViewData["roleId"] = id; var ManagerRole = await _managerRoleService.FindManagerRoleAsync(id); if (ManagerRole != null) ViewData["roleName"] = ManagerRole.RoleName; else ViewData["roleName"] = "未知角色"; #region 查出已經分派權限的roleId 對應的MenuId 引數id=角色id List<int> menuIds = new List<int>(); menuIds.Add(-1); if (!string.IsNullOrEmpty(id.ToString())) { var rolepermissions = await _rolePermissionService.GetRolePermissionAsync(id); foreach (RolePermission rp in rolepermissions) { menuIds.Add(rp.MenuId); } } #endregion #region 系結前臺CheckBox var items = await _menuService.GetMenusByParentId(0); List<Menu> lists = new List<Menu>(); List<RolePermission> listRolePermission = new List<RolePermission>(); foreach (Menu m in items) { lists.Add(m); var items2 = await _menuService.GetMenusByParentId(m.Id); foreach (Menu mu in items2) { lists.Add(mu); } } var model = new MenuView() { MenuList = lists, MenuIds = menuIds }; return View(model); #endregion } public async Task<IActionResult> EditAsync(int roleid,string idString) { string Msg = "保存成功"; #region 先洗掉舊的 await _rolePermissionService.DeleteRolePermissionByRoleId(roleid); #endregion #region 再添加新的 if (idString.Length > 0) { if (idString.IndexOf(',') > 0) { string[] ids = idString.Split(','); List<RolePermission> rps = new List<RolePermission>(); foreach (string id in ids) { RolePermission rp = new RolePermission(); rp.RoleId = roleid; rp.MenuId = int.Parse(id); rps.Add(rp); } await _rolePermissionService.AddRolePermissionAsync(rps); } } #endregion return View(Msg); } }
代碼折疊起來讀,就很好理解了,關于return View(Msg),這是個簡單處理,以后將統一回傳Json或標準資訊時,在詳細講解,你可以做這個Action轉向,或Return Ok();
(3.8)其中RolePermission對應的Service代碼如下:

public class RolePermissionService : IRolePermissionService { private readonly AppDbContext _appDbContext; public RolePermissionService(AppDbContext appDbContext) { _appDbContext = appDbContext; } public async Task<bool> AddRolePermissionAsync(RolePermission rolePermission) { await _appDbContext.RolePermission.AddAsync(rolePermission); var result = await _appDbContext.SaveChangesAsync(); return result == 1; } public async Task<bool> AddRolePermissionAsync(List<RolePermission> rps) { await _appDbContext.RolePermission.AddRangeAsync(rps); var result = await _appDbContext.SaveChangesAsync(); return result == 1; } public async Task<bool> DeleteRolePermissionAsync(int Id) { var delRolePermission = await _appDbContext.RolePermission.FirstOrDefaultAsync(x => x.Id == Id); if (delRolePermission != null) { _appDbContext.RolePermission.Remove(delRolePermission); } var result = await _appDbContext.SaveChangesAsync(); return result == 1; //注意(result==1 如果等式成立,則回傳true,說明洗掉成功) } public async Task<bool> DeleteRolePermissionByRoleId(int roleId) { List<RolePermission> delRolePermissions = await _appDbContext.RolePermission.Where(x => x.RoleId == roleId).ToListAsync(); if (delRolePermissions != null) { foreach(RolePermission rp in delRolePermissions) _appDbContext.RolePermission.Remove(rp); } var result = await _appDbContext.SaveChangesAsync(); return result == 1; //注意(result==1 如果等式成立,則回傳true,說明洗掉成功) } /// <summary> /// 查看一個角色與選單ID對應關系 /// </summary> /// <param name="Id"></param> /// <returns></returns> public async Task<RolePermission> FindRolePermissionAsync(int Id) { var item = await _appDbContext.RolePermission.Where(x => x.Id == Id).FirstOrDefaultAsync(); return item; } /// <summary> /// 獲取所有角色和選單 /// </summary> /// <returns></returns> public async Task<RolePermission[]> GetRolePermissionAsync() { var items = await _appDbContext.RolePermission.ToArrayAsync(); return items; } /// <summary> /// 獲取一個角色的所有選單 /// </summary> /// <param name="roleId"></param> /// <returns></returns> public async Task<RolePermission[]> GetRolePermissionAsync(int roleId) { var items = await _appDbContext.RolePermission.Where(x => x.RoleId == roleId).ToArrayAsync(); return items; } /// <summary> /// 更新角色權限 /// </summary> /// <param name="id"></param> /// <param name="rolePermission"></param> /// <returns></returns> public async Task<bool> UpdateRolePermissionAsync(int id, RolePermission rolePermission) { var oldRolePermission = await FindRolePermissionAsync(id); //找出舊物件 //將新值賦到舊物件上 oldRolePermission.MenuId = rolePermission.MenuId; oldRolePermission.RoleId = rolePermission.RoleId; oldRolePermission.Permission = rolePermission.Permission; //對舊物件執行更新 _appDbContext.Entry(oldRolePermission).State = EntityState.Modified; var result = await _appDbContext.SaveChangesAsync(); return result == 1; } }
注意有批量添加的功能,讀代碼時可稍加留意,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/536162.html
標籤:.NET Core
