在前面随笔《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界面的代码,这样在进行必定的函数扩展以及界面布局调整,就能够很是方便、高效的完整一个业务模块的开发工做了。