在前面隨筆《ABP開發框架先後端開發系列---(9)ABP框架的權限控制管理》中介紹了基於ABP框架服務構建的Winform客戶端,客戶端經過Web API調用的方式進行獲取數據,從而實現了對組織機構、角色、用戶、權限等管理,其中沒有涉及菜單部分,本篇隨筆介紹在ABP框架中實現菜單的管理,菜單是做爲Winform或者Web動態構建界面的一個重要元素,同時也是做爲角色權限控制的部分資源。html
通常狀況下,菜單的樹形列表的顯示能夠分爲多個節點,節點能夠收縮也能夠展開,固然節點是有不一樣的圖標的了。這樣就能夠把不少功能點整合在一個樹列表裏面了,樹的節點也能夠分爲不少級別,不少層次數據庫
若是咱們想按照業務的範疇來區分,也能夠分爲多個模塊展現,相似選項卡的方式,一個模塊的功能菜單列表集合在一塊兒展現,以下所示。後端
上面是我Winform開發框架和混合式開發框架的Winform界面中呈現菜單的界面,對於ABP開發框架來講,咱們也只是獲取數據方式不一樣,業務範疇的管理也沒有什麼不同,咱們依舊能夠在服務器端配置好系統的菜單記錄,而後基於ABP的Winform界面,一樣管理這些內容便可。服務器
下面是ABP框架中對於菜單資源管理的列表界面。app
左邊咱們經過TreeList列表進行展現,右側經過分頁控件列表的方式進行展現,仍是比較標準的Winform界面展現。框架
編輯或者建立菜單的界面以下所示。async
菜單對於角色來講,應該是一種界面資源,能夠經過配置進行管理對應角色用戶的菜單。ide
爲了開發菜單模塊,咱們須要先定義好菜單的存儲數據表,定義菜單表和角色菜單的中間關係表以下所示。函數
這個菜單模塊定位爲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調用類的簡化處理》簡化後的處理代碼以下所示。
菜單的管理總體操做和常規的業務表處理同樣,沒有太多特殊的地方,下面介紹一下角色包含菜單的管理操做。
前面介紹了角色包含菜單的管理界面以下所示。
界面主要是列出全部菜單,並勾選上該角色可使用的菜單。這個角色包含的菜單和角色包含的權限處理上比較類似。
首先咱們須要定義一個角色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界面的代碼,這樣在進行必定的函數擴展以及界面佈局調整,就能夠很是方便、高效的完整一個業務模塊的開發工做了。