在前面隨筆《ABP開發框架前後端開發系列---(9)ABP框架的許可權控制管理》中介紹了基於ABP框架服務構建的Winform客戶端,客戶端通過Web API調用的方式進行獲取數據,從而實現了對組織機構、角色、用戶、許可權等管理,其中沒有涉及菜單部分,本篇隨筆介紹在ABP框架中實現菜單的管理,菜單是作為... ...
在前面隨筆《ABP開發框架前後端開發系列---(9)ABP框架的許可權控制管理》中介紹了基於ABP框架服務構建的Winform客戶端,客戶端通過Web API調用的方式進行獲取數據,從而實現了對組織機構、角色、用戶、許可權等管理,其中沒有涉及菜單部分,本篇隨筆介紹在ABP框架中實現菜單的管理,菜單是作為Winform或者Web動態構建界面的一個重要元素,同時也是作為角色許可權控制的部分資源。
1、菜單的列表展示和管理
一般情況下,菜單的樹形列表的顯示可以分為多個節點,節點可以收縮也可以展開,當然節點是有不同的圖標的了。這樣就可以把很多功能點整合在一個樹列表裡面了,樹的節點也可以分為很多級別,很多層次
如果我們想按照業務的範疇來區分,也可以分為多個模塊展示,類似選項卡的方式,一個模塊的功能菜單列表集合在一起展示,如下所示。
上面是我Winform開發框架和混合式開發框架的Winform界面中呈現菜單的界面,對於ABP開發框架來說,我們也只是獲取數據方式不同,業務範疇的管理也沒有什麼不一樣,我們依舊可以在伺服器端配置好系統的菜單記錄,然後基於ABP的Winform界面,同樣管理這些內容即可。
下麵是ABP框架中對於菜單資源管理的列表界面。
左邊我們通過TreeList列表進行展示,右側通過分頁控制項列表的方式進行展示,還是比較標準的Winform界面展示。
編輯或者創建菜單的界面如下所示。
菜單對於角色來說,應該是一種界面資源,可以通過配置進行管理對應角色用戶的菜單。
2、菜單模塊的實現邏輯
為了開髮菜單模塊,我們需要先定義好菜單的存儲數據表,定義菜單表和角色菜單的中間關係表如下所示。
這個菜單模塊定位為Web和Winform都通用的,因此菜單表中增加多了一些欄位信息。
在資料庫里增加這兩個表後,就可以使用代碼生成工具進行框架代碼的生成和Winform界面代碼的生成了。
生成框架後,對應的應用服務層類代碼如下所示。
這個生成的類,預設具有基類的增刪改查分頁等介面方法,同時我們也會生成對應的Web API Caller層的類代碼,代碼如下所示。
Winform界面生成標準界面後進行佈局的一定調整,左側增加TreeList控制項,設計界面如下所示。
獲取列表數據的函數定義在GetData函數裡面,函數代碼如下所示。
/// <summary> /// 獲取數據 /// </summary> /// <returns></returns> private async Task<IPagedResult<MenuDto>> GetData() { MenuPagedDto pagerDto = null; if (advanceCondition != null) { pagerDto = new MenuPagedDto(this.winGridViewPager1.PagerInfo); pagerDto = dlg.GetPagedResult(pagerDto); } else if(!IsNormalSearch && this.tree.FocusedNode != null) { //構建分頁的條件和查詢條件 pagerDto = new MenuPagedDto(this.winGridViewPager1.PagerInfo) { PID = string.Concat(this.tree.FocusedNode.GetValue(Id_FieldName)) }; } else { //構建分頁的條件和查詢條件 pagerDto = new MenuPagedDto(this.winGridViewPager1.PagerInfo) { //添加所需條件 Name = this.txtName.Text.Trim(), WinformType = this.txtWinformType.Text.Trim() }; } var result = await MenuApiCaller.Instance.GetAll(pagerDto); return result; }
分頁控制項的數據綁定代碼如下所示,這些都是根據Winform界面配置自動生成的代碼。
this.winGridViewPager1.DisplayColumns = "EmbedIcon,Name,Seq,Visible,Expand,WinformType,Tag,CreationTime"; this.winGridViewPager1.ColumnNameAlias = await MenuApiCaller.Instance.GetColumnNameAlias();//欄位列顯示名稱轉義 //獲取分頁數據列表 var result = await GetData(); //設置所有記錄數和列表數據源 this.winGridViewPager1.PagerInfo.RecordCount = result.TotalCount; //需先於DataSource的賦值,更新分頁信息 this.winGridViewPager1.DataSource = result.Items;
而TreeList列表是我們後來增加上去的,需要額外進行數據的綁定和處理,初始化樹列表處理代碼如下所示。
/// <summary> /// 初始化樹控制項 /// </summary> private void InitTree() { this.tree.Columns.Clear(); //控制項擴展函數封裝處理 this.tree.CreateColumn("Name", "菜單名稱", 160, true); this.tree.InitTree("Id", "PID", null, false, false); //設置樹的圖標集合及逐級圖標 this.tree.SelectImageList = this.imageCollection1; this.tree.CustomDrawNodeImages += (object sender, CustomDrawNodeImagesEventArgs e) => { int maxCount = this.imageCollection1.Images.Count; var index = e.Node.Level < maxCount ? e.Node.Level : 0; e.SelectImageIndex = index; }; //初始化樹節點選擇事件 this.tree.FocusedNodeChanged += delegate (object sender, FocusedNodeChangedEventArgs e) { this.FocusedNodeChanged(); }; }
獲取列表數據並綁定樹列表的數據源如下所示
/// <summary> /// 綁定樹的數據源 /// </summary> private async Task BindTree() { var pageDto = new MenuPagedDto(); var result = await MenuApiCaller.Instance.GetAll(pageDto); this.tree.DataSource = result.Items; this.tree.ExpandAll(); }
而界面顯示的時候,載入並顯示左側樹列表數據如下代碼所示。
private async void FrmMenu_Load(object sender, EventArgs e) { //列表信息 InitTree(); InitSearchControl(); await BindTree(); }
刪除菜單的時候,我們一般想把當前菜單和下麵的子菜單一併級聯刪除,實現這個方法,我們需要在服務端自定義實現,如下是應用服務層的實現方法。
/// <summary> /// 移除節點和子節點 /// </summary> /// <param name="input"></param> /// <returns></returns> [UnitOfWork] public virtual async Task DeleteWithSubNode(EntityDto<string> input) { var children = await _repository.GetAllListAsync(ou => ou.PID == input.Id); foreach (var child in children) { await DeleteWithSubNode(new EntityDto<string>(child.Id));//遞歸刪除 } await _repository.DeleteAsync(input.Id); }
我們這裡顯示聲明瞭UnitOfWork標記,說明這個操作的原子性,全部成功就成功,否則失敗的處理。
而客戶端的Web API 封裝調用類,對這個Web API介面的封裝,根據上篇隨筆《ABP開發框架前後端開發系列---(10)Web API調用類的簡化處理》簡化後的處理代碼如下所示。
3、角色菜單管理
菜單的管理整體操作和常規的業務表處理一樣,沒有太多特殊的地方,下麵介紹一下角色包含菜單的管理操作。
前面介紹了角色包含菜單的管理界面如下所示。
界面主要是列出所有菜單,並勾選上該角色可以使用的菜單。這個角色包含的菜單和角色包含的許可權處理上比較相似。
首先我們需要定義一個角色DTO對象中的菜單集合屬性,如下所示。
在界面上獲取勾選上的許可權和菜單ID集合,存儲在對應的列表裡面。
/// <summary> /// 編輯或者保存狀態下取值函數 /// </summary> /// <param name="info"></param> private void SetInfo(RoleDto info) { info.DisplayName = txtDisplayName.Text; info.Name = txtName.Text; info.Description = txtDescription.Text; info.Permissions = GetNodeValues(this.tree, "Name"); info.Menus = GetNodeValues(this.treeMenu, "Id"); }
在應用服務層的RoleAppService類裡面,我們創建或者更新角色的時候,需要更新它的許可權和菜單資源,如下代碼是創建角色的函數。
/// <summary> /// 創建角色對象 /// </summary> /// <param name="input"></param> /// <returns></returns> public override async Task<RoleDto> Create(CreateRoleDto input) { CheckCreatePermission(); var role = ObjectMapper.Map<Role>(input); role.SetNormalizedName(); CheckErrors(await _roleManager.CreateAsync(role)); await CurrentUnitOfWork.SaveChangesAsync(); //It's done to get Id of the role. await UpdateGrantedPermissions(role, input.Permissions); await UpdateGrantedMenus(role, input.Menus); return MapToEntityDto(role); }
同理,更新角色一樣處理這兩個部分的資源
/// <summary> /// 更新角色對象 /// </summary> /// <param name="input"></param> /// <returns></returns> public override async Task<RoleDto> Update(RoleDto input) { CheckUpdatePermission(); var role = await _roleManager.GetRoleByIdAsync(input.Id); ObjectMapper.Map(input, role); CheckErrors(await _roleManager.UpdateAsync(role)); await UpdateGrantedPermissions(role, input.Permissions); await UpdateGrantedMenus(role, input.Menus); return MapToEntityDto(role); }
以上就是菜單的管理,和角色包含菜單的維護操作,整個開發過程主要就是使用代碼生成工具來快速生成框架各個層的代碼,以及Winform界面的代碼,這樣在進行一定的函數擴展以及界面佈局調整,就可以非常方便、高效的完整一個業務模塊的開發工作了。