我们来创建动态菜单吧

首先,先对动态菜单的概念、操作、流程进行约束:
1.Host和各个Tenant有自己的自定义菜单
2.Host和各个Tenant的权限与自定义菜单相关联
2.Tenant有一套默认的菜单,规定对应的TenantId=-1,在添加租户时自动将标准菜单和标准菜单的权限初始化到添加的租户

一、先实现菜单在数据库中的增删改查

第一步:创建表、实体,添加DbContext

我们需要创建一个菜单表,延续Abp的命名方法,表名叫AbpMenus吧(菜单和权限、验证我们要关联,所以文件尽量放在Authorization文件夹下)

把创建的实体放在AbpLearn.Core/Authorization下面,新建一个Menus文件夹,再创建Menus实体

    public class AbpMenus : Entity<int>
{
public string MenuName { set; get; }
public string PageName { set; get; }
public string Name { set; get; }
public string Url { set; get; }
public string Icon { set; get; }
public int ParentId { set; get; }
public bool IsActive { set; get; }
public int Orders { set; get; }
public int? TenantId { set; get; }
}
如果翻过源码中实体的定义,可以发现很多实体的继承,例如:

1.继承接口 IMayHaveTenant,继承后生成的sql语句将自动增加TenantId的查询条件,表中必须包含TenantId列
2.继承接口 IPassivable,继承后表中必须包含IsActive列
3.继承接口 FullAuditedEntity<TPrimaryKey> TPrimaryKey可以是long、int等值类型,必须包含IsDeleted、DeleterUserId、DeletionTime,其中这个接口
还继承了AuditedEntity<TPrimaryKey>, IFullAudited, IAudited, ICreationAudited, IHasCreationTime, IModificationAudited, IHasModificationTime, IDeletionAudited, IHasDeletionTime, ISoftDelete,这些父类型、接口的定义自己F12就可以看到

AbpLearn.EntityFrameworkCore/EntityFrameworkCore/AbpLearnDbContext.cs增加DbSet

public class AbpLearnDbContext : AbpZeroDbContext<Tenant, Role, User, AbpLearnDbContext>
{
/* Define a DbSet for each entity of the application */ public AbpLearnDbContext(DbContextOptions<AbpLearnDbContext> options)
: base(options)
{ } public DbSet<AbpMenus> AbpMenus { set; get; } }

再去数据库中添加AbpMenus表 字段长度请自行调整

DROP TABLE IF EXISTS `AbpMenus`;
CREATE TABLE `AbpMenus` (
`Id` int NOT NULL AUTO_INCREMENT,
`MenuName` varchar(50) DEFAULT NULL,
`PageName` varchar(50) DEFAULT NULL,
`LName` varchar(50) DEFAULT NULL,
`Url` varchar(50) DEFAULT NULL,
`Icon` varchar(20) DEFAULT NULL,
`ParentId` int DEFAULT NULL,
`IsActive` bit(1) NOT NULL DEFAULT b'0',
`Orders` int DEFAULT NULL,
`TenantId` int DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

第二步:添加Service和Dto

AbpLearn.Application/Authorization下添加Menus文件夹,然后添加IMenusAppService、MenusAppService,然后添加Dto文件夹

第三步:添加控制器和前台页面、js

Controller文件,MenusController.cs

前台添加Menus及对应的js文件,可以简单省事的把其他文件夹复制粘贴一份,然后关键词修改下

这些文件太多了,我会把这套代码上传到github中,文章最低部会把链接挂出来

添加完之后我们就可以生成预览一下Menus,因为SetNavigation中未将Menus的url加进去,我们自己手打链接进入

此时, 我们的菜单这一块的crud已经做好了,我们可以看到有一个Host管理员这个部分是什么意思哪?

我们为了在当前Host中可以控制所有租户的菜单和权限,将当前Host、标准菜单、租户做一个select,代码如下

    public class ChangeModalViewModel
{
public int? TenantId { get; set; } public string TenancyName { get; set; } public int? TenantMenuType { get; set; } public List<ComboboxItemDto> TeneacyItems { get; set; }
}
        public async Task<IActionResult> IndexAsync(int? id = )
{
var loginTenant = id <= ? null : _tenantManager.GetById((int)id); var viewModel = new ChangeModalViewModel
{
TenancyName = loginTenant?.TenancyName,
TenantId = id
}; viewModel.TeneacyItems = _tenantManager.Tenants
.Select(p => new ComboboxItemDto(p.Id.ToString(), p.Name) { IsSelected = viewModel.TenancyName == p.TenancyName })
.ToList(); viewModel.TeneacyItems.Add(new ComboboxItemDto("","Host管理员") { IsSelected = id == }); viewModel.TeneacyItems.Add(new ComboboxItemDto("-1", "默认菜单") { IsSelected = id == - }); ViewBag.LoginInfo = await _sessionAppService.GetCurrentLoginInformations(); return View(viewModel);
}

然后在Index.cshtml中添加或修改

@model ChangeModalViewModel  // 添加

@await Html.PartialAsync("~/Views/Menus/Index.AdvancedSearch.cshtml", Model)  //修改

@await Html.PartialAsync("~/Views/Menus/_CreateModal.cshtml",Model.TenantId)  //修改

//添加

$("#ChangeTenancyName").change(function (e) {
     location.href = "/Menus/Index/" + this.options[this.selectedIndex].value;
  });

修改_CreateModal.cshtml

@using Abp.Authorization.Users
@using Abp.MultiTenancy
@using AbpLearn.MultiTenancy
@using AbpLearn.Web.Models.Common.Modals
@model int
@{
Layout = null;
}
<div class="modal fade" id="MenuCreateModal" tabindex="-1" role="dialog" aria-labelledby="MenuCreateModalLabel" data-backdrop="static">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
@await Html.PartialAsync("~/Views/Shared/Modals/_ModalHeader.cshtml", new ModalHeaderViewModel(L("CreateNewMenu")))
<form name="systemMenuCreateForm" role="form" class="form-horizontal">
<div class="modal-body">
<div class="form-group row required">
<label class="col-md-3 col-form-label">@L("MenuName")</label>
<div class="col-md-9">
<input type="text" name="MenuName" class="form-control" required minlength="">
</div>
</div>
<div class="form-group row required">
<label class="col-md-3 col-form-label">@L("LName")</label>
<div class="col-md-9">
<input type="text" name="LName" class="form-control" required>
</div>
</div>
<div class="form-group row required">
<label class="col-md-3 col-form-label">@L("Url")</label>
<div class="col-md-9">
<input type="text" name="Url" class="form-control">
</div>
</div>
<div class="form-group row">
<label class="col-md-3 col-form-label">@L("PageName")</label>
<div class="col-md-9">
<input type="text" name="PageName" class="form-control">
</div>
</div>
<div class="form-group row">
<label class="col-md-3 col-form-label">@L("ParentId")</label>
<div class="col-md-9">
<input type="text" name="ParentId" class="form-control">
</div>
</div>
<div class="form-group row">
<label class="col-md-3 col-form-label">@L("Orders")</label>
<div class="col-md-9">
<input type="text" name="Orders" class="form-control">
</div>
</div>
<div class="form-group row">
<label class="col-md-3 col-form-label" for="CreateMenuIsActive">@L("IsActive")</label>
<div class="col-md-9">
<input id="CreateMenuIsActive" type="checkbox" name="IsActive" value="true" checked />
</div>
</div>
</div>
<input type="hidden" name="TenantId" value="@(Model)" />
@await Html.PartialAsync("~/Views/Shared/Modals/_ModalFooterWithSaveAndCancel.cshtml")
</form>
</div>
</div>
</div>

修改_EditModal.cshtml

@using AbpLearn.Authorization.Menus.Dto
@using AbpLearn.Web.Models.Common.Modals
@model MenuDto
@{
Layout = null;
}
@await Html.PartialAsync("~/Views/Shared/Modals/_ModalHeader.cshtml", new ModalHeaderViewModel(L("EditMenu")))
<form name="MenuEditForm" role="form" class="form-horizontal">
<input type="hidden" name="Id" value="@Model.Id" />
<div class="modal-body">
<div class="form-group row required">
<label class="col-md-3 col-form-label" for="tenancy-name">@L("MenuName")</label>
<div class="col-md-9">
<input id="tenancy-name" type="text" class="form-control" name="MenuName" value="@Model.MenuName" required maxlength="" minlength="">
</div>
</div>
<div class="form-group row required">
<label class="col-md-3 col-form-label" for="name">@L("LName")</label>
<div class="col-md-9">
<input id="name" type="text" class="form-control" name="LName" value="@Model.LName" required maxlength="">
</div>
</div>
<div class="form-group row required">
<label class="col-md-3 col-form-label" for="name">@L("Url")</label>
<div class="col-md-9">
<input id="name" type="text" class="form-control" name="Url" value="@Model.Url" required maxlength="">
</div>
</div> <div class="form-group row required">
<label class="col-md-3 col-form-label" for="name">@L("PageName")</label>
<div class="col-md-9">
<input id="name" type="text" class="form-control" name="PageName" value="@Model.PageName" required maxlength="">
</div>
</div>
<div class="form-group row required">
<label class="col-md-3 col-form-label" for="name">@L("ParentId")</label>
<div class="col-md-9">
<input id="name" type="text" class="form-control" name="ParentId" value="@Model.ParentId" required maxlength="">
</div>
</div>
<div class="form-group row required">
<label class="col-md-3 col-form-label" for="name">@L("Orders")</label>
<div class="col-md-9">
<input id="name" type="text" class="form-control" name="Orders" value="@Model.Orders" required maxlength="">
</div>
</div>
<div class="form-group row">
<label class="col-md-3 col-form-label" for="isactive">@L("IsActive")</label>
<div class="col-md-9">
<input id="isactive" type="checkbox" name="IsActive" value="true" @(Model.IsActive ? "checked" : "") />
</div>
</div>
</div>
@await Html.PartialAsync("~/Views/Shared/Modals/_ModalFooterWithSaveAndCancel.cshtml")
</form> <script src="~/view-resources/Views/Menus/_EditModal.js" asp-append-version="true"></script>

修改Index.AdvancedSearch.cshtml

@using AbpLearn.Web.Views.Shared.Components.TenantChange
@using Abp.Application.Services.Dto
@model ChangeModalViewModel <div class="abp-advanced-search">
<form id="MenusSearchForm" class="form-horizontal">
<input type="hidden" name="TenantId" value="@Model.TenantId" />
</form>
<div class="form-horizontal">
<div class="form-group">
@Html.DropDownList(
"ChangeTenancyNames",
Model.TeneacyItems.Select(i => i.ToSelectListItem()),
new { @class = "form-control edited", id = "ChangeTenancyName" })
</div>
</div>
</div>

因为在abp里面加载当前列表调用的是abp.services.app.menus.getAll方法,我们还需要对MenusAppService中的GetAllAsync做一下修改

    [Serializable]
public class MenusPagedResultRequestDto: PagedResultRequestDto, IPagedAndSortedResultRequest
{
public virtual int? TenantId { get; set; } public virtual string Sorting { get; set; } public virtual bool ShowAll { get; set; } }
        #region 查询全部菜单
/// <summary>
/// 查询全部菜单
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public override async Task<PagedResultDto<MenuDto>> GetAllAsync(MenusPagedResultRequestDto input)
{
IQueryable<AbpMenus> query; query = CreateFilteredQuery(input).Where(o => o.TenantId == (input.TenantId == ? null : input.TenantId)); var totalCount = await AsyncQueryableExecuter.CountAsync(query); query = ApplySorting(query, input);
if (!input.ShowAll) query = ApplyPaging(query, input); var entities = await AsyncQueryableExecuter.ToListAsync(query); return new PagedResultDto<MenuDto>(
totalCount,
entities.Select(MapToEntityDto).ToList()
);
} #endregion

这样,我们在选中下面中的任意一个Tenant时,将会跳到对应的菜单里面了

我们先把Host管理员菜单和默认菜单配置一下

二、实现添加租户时,初始化标准菜单和权限

首先我们找到添加租户的地方,去TenantAppService里面去找,可以看到有CreateAsync的重写

        public override async Task<TenantDto> CreateAsync(CreateTenantDto input)
{
CheckCreatePermission(); // Create tenant
var tenant = ObjectMapper.Map<Tenant>(input);
tenant.ConnectionString = input.ConnectionString.IsNullOrEmpty()
? null
: SimpleStringCipher.Instance.Encrypt(input.ConnectionString); var defaultEdition = await _editionManager.FindByNameAsync(EditionManager.DefaultEditionName);
if (defaultEdition != null)
{
tenant.EditionId = defaultEdition.Id;
} await _tenantManager.CreateAsync(tenant);
await CurrentUnitOfWork.SaveChangesAsync(); // To get new tenant's id. // Create tenant database
_abpZeroDbMigrator.CreateOrMigrateForTenant(tenant); // We are working entities of new tenant, so changing tenant filter
using (CurrentUnitOfWork.SetTenantId(tenant.Id))
{
// Create static roles for new tenant
CheckErrors(await _roleManager.CreateStaticRoles(tenant.Id)); await CurrentUnitOfWork.SaveChangesAsync(); // To get static role ids // Grant all permissions to admin role
var adminRole = _roleManager.Roles.Single(r => r.Name == StaticRoleNames.Tenants.Admin);
await _roleManager.GrantAllPermissionsAsync(adminRole); // Create admin user for the tenant
var adminUser = User.CreateTenantAdminUser(tenant.Id, input.AdminEmailAddress);
await _userManager.InitializeOptionsAsync(tenant.Id);
CheckErrors(await _userManager.CreateAsync(adminUser, User.DefaultPassword));
await CurrentUnitOfWork.SaveChangesAsync(); // To get admin user's id // Assign admin user to role!
CheckErrors(await _userManager.AddToRoleAsync(adminUser, adminRole.Name));
await CurrentUnitOfWork.SaveChangesAsync();
} return MapToEntityDto(tenant);
}

我们需要做的是,在 using (CurrentUnitOfWork.SetTenantId(tenant.Id)) 的内部尾部添加赋予菜单和权限的方法即可

赋予菜单和权限的方法我们分开写,都放在MenusAppService中,

    public interface IMenusAppService : IAsyncCrudAppService<MenuDto, int, MenusPagedResultRequestDto, CreateMenuDto, MenuDto>
{
/// <summary>
/// 赋予默认菜单
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task GiveMenusAsync(EntityDto<int> input); /// <summary>
/// 赋予当前租户Admin角色菜单权限
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task GivePermissionsAsync(EntityDto<int> input);
}
        #region 赋予默认菜单
public async Task GiveMenusAsync(EntityDto<int> input)
{
if (input.Id > )
{
var tenant = await _tenantManager.GetByIdAsync(input.Id); using (_unitOfWorkManager.Current.SetTenantId(tenant.Id))
{
var query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == tenant.Id); var systemMenus = await AsyncQueryableExecuter.ToListAsync(query); if (!systemMenus.Any())
{
query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == -); var defaultMenus = await AsyncQueryableExecuter.ToListAsync(query);
if (defaultMenus.Any())
{
foreach (var entity in defaultMenus)
{
await CreateAsync(new AbpMenus()
{
LName = entity.LName,
MenuName = entity.MenuName,
PageName = entity.PageName,
Icon = entity.Icon,
Url = entity.Url,
IsActive = entity.IsActive,
Orders = entity.Orders,
ParentId = entity.ParentId,
TenantId = tenant.Id
});
}
}
}
}
} }
#endregion #region 赋予当前租户Admin角色菜单权限
/// <summary>
/// 赋予当前租户Admin角色菜单权限
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task GivePermissionsAsync(EntityDto<int> input)
{
if (input.Id > )
{
var tenant = await _tenantManager.GetByIdAsync(input.Id); using (_unitOfWorkManager.Current.SetTenantId(tenant.Id))
{
var adminRoles = await _roleRepository.GetAllListAsync(o => o.Name == StaticRoleNames.Tenants.Admin && o.TenantId == tenant.Id);
if (adminRoles.FirstOrDefault() != null)
{
var adminRole = adminRoles.FirstOrDefault(); var query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == tenant.Id); var systemMenus = await AsyncQueryableExecuter.ToListAsync(query); var permissions = ConvertTenantPermissions(systemMenus); //await _roleManager.ResetAllPermissionsAsync(adminRole.FirstOrDefault()); //重置授权 var active_BatchCount = ;
var active_permissions = ConvertTenantPermissions(systemMenus.Where(o => o.IsActive).ToList());
for (int i = ; i < active_permissions.Count(); i += )//每次后移5位
{
//await _roleManager.SetGrantedPermissionsAsync(adminRole.FirstOrDefault().Id, active_permissions.Take(active_BatchCount).Skip(i));
foreach (var notActive_permission in active_permissions.Take(active_BatchCount).Skip(i))
{
await _roleManager.GrantPermissionAsync(adminRole, notActive_permission);
}
active_BatchCount += ;//每次从数组中选出N+10位,skip前N位
} var notActive_BatchCount = ;
var notActive_permissions = ConvertTenantPermissions(systemMenus.Where(o => !o.IsActive).ToList());
for (int i = ; i < notActive_permissions.Count(); i += )//每次后移5位
{
foreach (var notActive_permission in notActive_permissions.Take(notActive_BatchCount).Skip(i))
{
await _roleManager.ProhibitPermissionAsync(adminRole, notActive_permission);
}
notActive_BatchCount += ;//每次从数组中选出N+10位,skip前N位
}
}
else
{
throw new AbpDbConcurrencyException("未获取到当前租户的Admin角色!");
}
}
}
else
{
var adminRoles = await _roleRepository.GetAllListAsync(o => o.Name == StaticRoleNames.Tenants.Admin && o.TenantId == null);
if (adminRoles.FirstOrDefault() != null)
{
var adminRole = adminRoles.FirstOrDefault(); var query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == null || o.TenantId == ); var systemMenus = await AsyncQueryableExecuter.ToListAsync(query); //await _roleManager.ResetAllPermissionsAsync(adminRole.FirstOrDefault()); //重置授权 var active_BatchCount = ;
var active_permissions = ConvertHostPermissions(systemMenus.Where(o => o.IsActive).ToList());
for (int i = ; i < active_permissions.Count(); i += )//每次后移5位
{
//await _roleManager.SetGrantedPermissionsAsync(adminRole.FirstOrDefault().Id, active_permissions.Take(active_BatchCount).Skip(i));
foreach (var notActive_permission in active_permissions.Take(active_BatchCount).Skip(i))
{
await _roleManager.GrantPermissionAsync(adminRole, notActive_permission);
}
active_BatchCount += ;//每次从数组中选出N+10位,skip前N位
} var notActive_BatchCount = ;
var notActive_permissions = ConvertHostPermissions(systemMenus.Where(o => !o.IsActive).ToList());
for (int i = ; i < notActive_permissions.Count(); i += )//每次后移5位
{
foreach (var notActive_permission in notActive_permissions.Take(notActive_BatchCount).Skip(i))
{
await _roleManager.ProhibitPermissionAsync(adminRole, notActive_permission);
}
notActive_BatchCount += ;//每次从数组中选出N+10位,skip前N位
}
}
}
} public IEnumerable<Permission> ConvertTenantPermissions(IReadOnlyList<AbpMenus> systemMenus)
{
return systemMenus.Select(o => new Permission(o.PageName, L(o.MenuName), L(o.LName), MultiTenancySides.Tenant));
} public IEnumerable<Permission> ConvertHostPermissions(IReadOnlyList<AbpMenus> systemMenus)
{
return systemMenus.Select(o => new Permission(o.PageName, L(o.MenuName), L(o.LName), MultiTenancySides.Host));
}
#endregion

TenantAppService.cs中做一下修改

        public override async Task<TenantDto> CreateAsync(CreateTenantDto input)
{
CheckCreatePermission(); // Create tenant
var tenant = ObjectMapper.Map<Tenant>(input);
tenant.ConnectionString = input.ConnectionString.IsNullOrEmpty()
? null
: SimpleStringCipher.Instance.Encrypt(input.ConnectionString); var defaultEdition = await _editionManager.FindByNameAsync(EditionManager.DefaultEditionName);
if (defaultEdition != null)
{
tenant.EditionId = defaultEdition.Id;
} await _tenantManager.CreateAsync(tenant);
await CurrentUnitOfWork.SaveChangesAsync(); // To get new tenant's id. // Create tenant database
_abpZeroDbMigrator.CreateOrMigrateForTenant(tenant); // We are working entities of new tenant, so changing tenant filter
using (CurrentUnitOfWork.SetTenantId(tenant.Id))
{
// Create static roles for new tenant
CheckErrors(await _roleManager.CreateStaticRoles(tenant.Id)); await CurrentUnitOfWork.SaveChangesAsync(); // To get static role ids // Grant all permissions to admin role
var adminRole = _roleManager.Roles.Single(r => r.Name == StaticRoleNames.Tenants.Admin);
await _roleManager.GrantAllPermissionsAsync(adminRole); // Create admin user for the tenant
var adminUser = User.CreateTenantAdminUser(tenant.Id, input.AdminEmailAddress);
await _userManager.InitializeOptionsAsync(tenant.Id);
CheckErrors(await _userManager.CreateAsync(adminUser, User.DefaultPassword));
await CurrentUnitOfWork.SaveChangesAsync(); // To get admin user's id // Assign admin user to role!
CheckErrors(await _userManager.AddToRoleAsync(adminUser, adminRole.Name));
await CurrentUnitOfWork.SaveChangesAsync(); await _menusAppService.GiveMenusAsync(new EntityDto<int>() { Id = tenant.Id });
await CurrentUnitOfWork.SaveChangesAsync(); await _menusAppService.GivePermissionsAsync(new EntityDto<int>() { Id = tenant.Id });
await CurrentUnitOfWork.SaveChangesAsync();
} return MapToEntityDto(tenant);
}

现在我们添加租户企业1、企业2

现在菜单已经同步好了,我们去数据库看下权限的同步

TenantId:

null是Host

1是abp页面第一次加载时初始化的Default租户

2是我之前添加的旧的企业1,那个时候方法没写好,就把2的删掉了

3是企业2

4是企业1

由此可以看出,我们添加的菜单对应的PageName已经作为权限添加到权限表了

三、实现菜单修改后,权限赋予对应租户

这一个其实在二里面已经写好了,前台做一个按钮,赋予权限,调用一下就好了

例如:

Index.cshtml   //为什么要加getCurrentLoginInformationsOutput.Tenant == null的判断?是因为租户在进入菜单管理的地方,我们不给他们添加、赋予权限的权限

在/wwwroot/view-resources/Views/Menus/Index.js中添加

    $(document).on('click', '#GivePermissions', function (e) {
var tenantId = $(this).attr('data-tenant-id'); abp.message.confirm(
abp.utils.formatString(
"是否赋予当前租户管理员账号所有权限?",
"系统"
),
null,
(isConfirmed) => {
if (isConfirmed) {
_menuService
.givePermissions({
id: tenantId
})
.done(() => {
abp.notify.info("操作成功!");
_$menusTable.ajax.reload();
});
}
}
);
});

四、实现菜单的动态加载

https://www.cnblogs.com/wangpengzong/p/13089690.html中我们找到了菜单生成的地方,在最底部,通过NavigationManager来获取到Menus,这里其实有一个初始化方法(Initialize),调用的是AbpLearnNavigationProvider的SetNavigation方法来进行本地化,然后在

NavigationManager的非静态构造函数中去获取已经本地化的Menus,但是本地化Menus因为是在初始化时,程序的初始化我们无法获取到当前的Tenant信息,所以只能将获取Menus的地方推迟,放在倒数第二个类UserNavigationManager里面的GetMenuAsync方法中,我们来看下GetMenuAsync
        public async Task<UserMenu> GetMenuAsync(string menuName, UserIdentifier user)
{
var menuDefinition = _navigationManager.Menus.GetOrDefault(menuName);
if (menuDefinition == null)
{
throw new AbpException("There is no menu with given name: " + menuName);
} var userMenu = new UserMenu(menuDefinition, _localizationContext);
await FillUserMenuItems(user, menuDefinition.Items, userMenu.Items);
return userMenu;
}

第一句话获取menuDefinition是关键点,我们将menuDefinition修改为从数据库中获取,在AbpLearn.Application/Authorization/Menus下添加UserNavigationManager.cs

using Abp;
using Abp.Application.Features;
using Abp.Application.Navigation;
using Abp.Authorization;
using Abp.Dependency;
using Abp.Localization;
using Abp.MultiTenancy;
using Abp.Runtime.Session;
using AbpLearn.Authorization.Menus.Dto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace AbpLearn.Authorization.Menus
{
public class UserNavigationManager : IUserNavigationManager, ITransientDependency
{
public IAbpSession AbpSession { get; set; } private readonly INavigationManager _navigationManager;
private readonly ILocalizationContext _localizationContext;
private readonly IIocResolver _iocResolver;
private readonly IMenusAppService _menuAppService;
public IDictionary<string, MenuDefinition> Menus { get; private set; }
public MenuDefinition MainMenu
{
get { return Menus["MainMenu"]; }
}
public UserNavigationManager(
INavigationManager navigationManager,
ILocalizationContext localizationContext,
IMenusAppService menuAppService,
IIocResolver iocResolver)
{
_navigationManager = navigationManager;
_localizationContext = localizationContext;
_iocResolver = iocResolver;
AbpSession = NullAbpSession.Instance;
_menuAppService = menuAppService; } public async Task<UserMenu> GetMenuAsync(string menuName, UserIdentifier user)
{
Menus = new Dictionary<string, MenuDefinition>
{
{"MainMenu", new MenuDefinition("MainMenu", new LocalizableString("MainMenu", AbpConsts.LocalizationSourceName))}
}; var lists = await _menuAppService.GetAllAsync(new MenusPagedResultRequestDto() { ShowAll = true, TenantId = (loginInfo.Tenant == null ? 0 : loginInfo.Tenant.Id) });
var ParentMenu = lists.Items.Where(k => k.IsActive).ToList().Where(x => x.ParentId == ).ToList();
if (ParentMenu.Any())
{
ParentMenu.ForEach(g =>
{
var menu = new MenuItemDefinition(
g.LName,
MenuL(g.MenuName),
g.Icon,
g.Url,
false,
g.Orders
);
BuildSubMenu(menu, g.Id, lists.Items.Where(k => k.IsActive).ToList());
MainMenu.AddItem(menu);
});
} var menuDefinition = MainMenu;
if (menuDefinition == null)
{
throw new AbpException("There is no menu with given name: " + menuName);
}
var userMenu = new UserMenu();
userMenu.Name = menuDefinition.Name;
userMenu.DisplayName = menuDefinition.DisplayName.Localize(_localizationContext);
userMenu.CustomData = menuDefinition.CustomData;
userMenu.Items = new List<UserMenuItem>();
await FillUserMenuItems(user, menuDefinition.Items, userMenu.Items);
return userMenu;
} public async Task<IReadOnlyList<UserMenu>> GetMenusAsync(UserIdentifier user)
{
var userMenus = new List<UserMenu>(); foreach (var menu in _navigationManager.Menus.Values)
{
userMenus.Add(await GetMenuAsync(menu.Name, user));
} return userMenus;
}
public void BuildSubMenu(MenuItemDefinition menu, int parentId, List<MenuDto> list)
{
var nList = list.Where(x => x.ParentId == parentId).ToList();
if (nList != null && nList.Count > )
{
nList.ForEach(g =>
{
var subMenu = new MenuItemDefinition(
g.PageName,
MenuL(g.MenuName),
g.Icon,
g.Url,
false,
g.Orders
);
menu.AddItem(subMenu);
BuildSubMenu(subMenu, g.Id, list);
});
}
} private static ILocalizableString MenuL(string name)
{
return new LocalizableString(name, AbpLearnConsts.LocalizationSourceName);
}
private async Task<int> FillUserMenuItems(UserIdentifier user, IList<MenuItemDefinition> menuItemDefinitions, IList<UserMenuItem> userMenuItems)
{
//TODO: Can be optimized by re-using FeatureDependencyContext. var addedMenuItemCount = ; using (var scope = _iocResolver.CreateScope())
{
var permissionDependencyContext = scope.Resolve<PermissionDependencyContext>();
permissionDependencyContext.User = user; var featureDependencyContext = scope.Resolve<FeatureDependencyContext>();
featureDependencyContext.TenantId = user == null ? null : user.TenantId; foreach (var menuItemDefinition in menuItemDefinitions)
{
if (menuItemDefinition.RequiresAuthentication && user == null)
{
continue;
} if (menuItemDefinition.PermissionDependency != null &&
(user == null || !(await menuItemDefinition.PermissionDependency.IsSatisfiedAsync(permissionDependencyContext))))
{
continue;
} if (menuItemDefinition.FeatureDependency != null &&
(AbpSession.MultiTenancySide == MultiTenancySides.Tenant || (user != null && user.TenantId != null)) &&
!(await menuItemDefinition.FeatureDependency.IsSatisfiedAsync(featureDependencyContext)))
{
continue;
} var userMenuItem = new UserMenuItem(menuItemDefinition, _localizationContext);
if (menuItemDefinition.IsLeaf || (await FillUserMenuItems(user, menuItemDefinition.Items, userMenuItem.Items)) > )
{
userMenuItems.Add(userMenuItem);
++addedMenuItemCount;
}
}
} return addedMenuItemCount;
}
}
}

然后在Mvc项目的Startup.cs/ConfigureServices下增加

            services.AddScoped<IUserNavigationManager, UserNavigationManager>();

因为在abp中菜单被做做成了模块,在程序初始化时模块添加进去,但是我们将菜单修改成了每次读取数据库加载,那么我们就不需要加载这个模块了

在mvc项目的AbpLearnWebMvcModule.cs注释下面这句话

            //Configuration.Navigation.Providers.Add<AbpLearnNavigationProvider>();

将AbpLearnNavigationProvider.cs/SetNavigation方法的内容全部注释掉

预览一下mvc,用Host登录一下

用企业1登陆下,登录切换Host和Tenant,是在登录界面 Current tenant: 未选 (Change) 点击Change,在弹框中输入 E1(因为上面设置的企业1标识是E1),点击save,页面刷新后就变为了 Current tenant: E1 (Change) ,输入账号密码登录

OK,我们的动态菜单已经完成了

当然,我的菜单使用的是table来显示,你也可以使用tree来,我找到了一个jstree,下面修改一下

MenusAppService.cs

        #region 获取当前账户的菜单树
/// <summary>
/// 获取当前账户的菜单树
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<string> GetTreeAsync(MenusPagedResultRequestDto input)
{
var query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == input.TenantId); var systemMenus = await AsyncQueryableExecuter.ToListAsync(query); var childJObject = new JObject();
var openJObject = new JObject();
openJObject.Add("opened", true);
childJObject.Add("id", );
childJObject.Add("text", "根目录");
childJObject.Add("icon", "");
childJObject.Add("state", openJObject);
childJObject.Add("children", GetJArray(systemMenus, ));
return childJObject.ToString();
} #region 获取目录Array
/// <summary>
/// 获取目录Array
/// </summary>
/// <param name="systemMenus"></param>
/// <param name="parentdId"></param>
/// <returns></returns>
private JArray GetJArray(List<AbpMenus> systemMenus, int parentdId)
{
JArray jArray = new JArray();
foreach (var menu in systemMenus.Where(o => o.ParentId == parentdId))
{
var jObject = new JObject();
jObject.Add("id", menu.Id);
jObject.Add("text", menu.MenuName);
jObject.Add("icon", menu.Icon);
//jObject.Add("state", menu.Icon);
if (systemMenus.Any(o => o.ParentId == menu.Id))
{
jObject.Add("children", GetJArray(systemMenus, menu.Id));
}
jArray.Add(jObject);
}
return jArray;
} #endregion #endregion

前端Index.cshtml  jstree去https://github.com/vakata/jstree/zipball/3.3.8下载,下载后在mvc项目的wwwroot文件夹下添加jstree文件夹,下载文件的src里面内容全部赋值到jstree文件夹

注释掉table标签

添加jstree1

例如:

@section styles
{
<link href="~/jstree/themes/default/style.css" rel="stylesheet" />
} <div id="jstree1" style="width:100%;"></div> @section scripts
{
<environment names="Development">
<script src="~/view-resources/Views/Menus/Index.js" asp-append-version="true"></script>
</environment> <environment names="Staging,Production">
<script src="~/view-resources/Views/Menus/Index.min.js" asp-append-version="true"></script>
</environment>
<script type="application/javascript" src="~/jstree/jstree.js"></script>
<script type="application/javascript" src="~/jstree/jstree.contextmenu.js"></script>
<script type="text/javascript">
$(function () {
var _menuService = abp.services.app.menus; l = abp.localization.getSource('A_b_p'); $('#jstree1').jstree({
"core": {
"data": function (node, callback) {
var filter = $('#MenusSearchForm').serializeFormToObject(true);
this, _menuService.getTree(filter).done(function (result) {
callback.call(this, JSON.parse(result));
});
},
"themes": {
"variant": "large",//加大
"ellipsis": true //文字多时省略
},
"check_callback": true,
},
"plugins": ["contextmenu", "wholerow", "themes"],//"checkbox"
"contextmenu": {
select_node: false,
show_at_node: true,
"items": {
"create": {
"label": "新增子菜单",
"action": function (obj) {
var inst = jQuery.jstree.reference(obj.reference);
var clickedNode = inst.get_node(obj.reference);
if (parseInt(clickedNode.original.id) >= 0) {
$("#ParentId").val(clickedNode.original.id);
$("#MenuCreateModal").modal();
} else {
abp.notify.info("父节点获取出错");
}
},
},
"rename": {
"label": "修改",
"action": function (obj) {
var inst = jQuery.jstree.reference(obj.reference);
var clickedNode = inst.get_node(obj.reference);
if (parseInt(clickedNode.original.id) >= 0) {
abp.ajax({
url: abp.appPath + 'Menus/EditModal?menuId=' + clickedNode.original.id,
type: 'POST',
dataType: 'html',
success: function (content) {
$("#MenuEditModal").modal();
$('#MenuEditModal div.modal-content').html(content);
},
error: function (e) { }
});
} else {
abp.notify.info("菜单获取出错");
}
}
},
"delete": {
"label": "更改菜单状态",
"action": function (obj) {
var inst = jQuery.jstree.reference(obj.reference);
var clickedNode = inst.get_node(obj.reference);
abp.message.confirm(
abp.utils.formatString("是否" + (clickedNode.original.state.disabled?"启用":"禁用") + "当前菜单:" + clickedNode.original.text + "?"),
null,
(isConfirmed) => {
if (isConfirmed) {
_menuService
.delete({
id: clickedNode.original.id
})
.done(() => {
abp.notify.info(l('SuccessfullyDeleted'));
location.reload();
});
}
}
);
}, }
}
}
}).on('select_node.jstree', function (event, data) {
console.log(data.node);
}).on('changed.jstree', function (event, data) {
console.log("-----------changed.jstree");
console.log("action:" + data.action);
console.log(data.node);
}); }); </script>
}

预览一下吧

本文github:https://github.com/wangpengzong/AbpLearn

下一篇开始动态权限

.net core3.1 abp动态菜单和动态权限(动态菜单实现和动态权限添加) (三)的更多相关文章

  1. .net core3.1 abp动态菜单和动态权限(思路) (二)

    ps:本文需要先把abp的源码下载一份来下,跟着一起找实现,更容易懂 在abp中,对于权限和菜单使用静态来管理,菜单的加载是在登陆页面的地方(具体是怎么知道的,浏览器按F12,然后去sources中去 ...

  2. python 全栈开发,Day108(客户管理之权限控制,客户管理之动态"一级"菜单,其他应用使用rbac组件,django static文件的引入方式)

    一.客户管理之权限控制 昨天的作业,有很多不完善的地方 下载代码,基本实现权限验证 https://github.com/987334176/luffy_permission/archive/v1.2 ...

  3. [2017-08-21]Abp系列——如何使用Abp插件机制(注册权限、菜单、路由)

    本系列目录:Abp介绍和经验分享-目录 Abp的模块系统支持插件机制,可以在指定目录中放置模块程序集,然后应用程序启动时会搜索该目录,加载其中所有程序集中的模块. 如何使用这套机制进行功能插件化开发? ...

  4. 业务逻辑:五、完成认证用户的动态授权功能 六、完成Shiro整合Ehcache缓存权限数据

    一. 完成认证用户的动态授权功能 提示:根据当前认证用户查询数据库,获取其对应的权限,为其授权 操作步骤: 在realm的授权方法中通过使用principals对象获取到当前登录用户 创建一个授权信息 ...

  5. [Swift通天遁地]一、超级工具-(1)动态标签:给UILabel文字中的Flag和url添加点击事件

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  6. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(三)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  7. 菜单和按钮-EasyUI Menu 菜单、EasyUI Linkbutton 链接按钮、EasyUI Menubutton 菜单按钮、EasyUI Splitbutton 分割按钮

    EasyUI Menu 菜单 通过 $.fn.menu.defaults 重写默认的 defaults. 菜单(Menu)通常用于上下文菜单.它是创建其他菜单组件(比如:menubutton.spli ...

  8. 关于MFC主菜单和右键弹出菜单

    一.主菜单.弹出菜单和右键菜单的概念: 主菜单是窗口顶部的菜单,一个窗口或对话框只能有一个主菜单,但是主菜单可以被更改(SetMenu()更改): 创建方式:CMenu::CreateMenu(voi ...

  9. iview可收缩侧边菜单实现(支持二级菜单)

    想用iview做一个可以伸缩的侧边菜单栏,效果如下: 1.侧边栏收缩前:可以通过点击菜单分类展开子菜单项: 2.可以让用户点击图标动态收缩菜单栏: 3.侧边栏收缩后:只显示菜单分类的图标,鼠标放置在菜 ...

  10. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(20)-权限管理系统-根据权限获取菜单

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(20)-权限管理系统-根据权限获取菜单 不知不觉到20讲,真是漫长的日子,可惜最近工作挺忙,要不可以有更多 ...

随机推荐

  1. GreenPlum执行gpfdist报错:libssl.so.1.0.0: cannot open shared object file: No such file or directory

    当你报这个错时,是因为你执行 ./gpfdist 时默认去找寻/usr/lib64的环境依赖,没有去找寻你gpfdist安装目录中lib的目录. 解决方法: 根据报错提示,将你gpfdist安装目录中 ...

  2. Canvas 画圆

    原文地址:http://hi.baidu.com/lj2tj/item/557d8d1a65adfa721009b58b --------------------------------------- ...

  3. wxss--外联样式与内联样式

    外联样式 有样式表a.wxss和index.wxss如下: /**a.wxss**/ .container1{ border: 1px solid #000; } /**index.wxss**/ . ...

  4. view组件的封装是否需要特有模型?

    必须需要. 现在接手的老项目,所有自定义组件全部使用的原始的全量数据,作为模型给view用来展示. 结果发现,基本数据的选择错误,需要选择另一个数据作为基本数据,这导致一个很麻烦的问题,需要改动全部的 ...

  5. airflow的安装和使用 - 完全版

    之前试用了azkaban一小段时间,虽然上手快速方便,但是功能还是太简单,不够灵活. Airflow使用代码来管理任务,这样应该是最灵活的,决定试一下. 我是python零基础,在使用airflow的 ...

  6. vi和软件安装

    一 vi编辑器简介 vim     全屏幕纯文本编辑器 二  vim使用 1   vi 模式 vi  文件名 命令模式 输入模式 末行模式 命令---->输入  a:追加  i:插入  o:打开 ...

  7. 不可不知的 7 个 JDK 命令

    这篇文章主要来介绍下 JDK 内置的命令,话不多说,让我们开始吧! javap 使用 javap 可以查看 Java 字节码反编译的源文件,javap 的命令格式如下: 下面来演示下用 javap - ...

  8. 基于 kubeadm 搭建高可用的kubernetes 1.18.2 (k8s)集群一 环境准备

    本k8s集群参考了 Michael 的 https://gitee.com/pa/kubernetes-ha-kubeadm-private 这个项目,再此表示感谢! Michael的项目k8s版本为 ...

  9. 初窥 BB-Framework

     

  10. Dell KACE K1000 poc

    POST /service/krashrpt.php HTTP/1.1 Host: xxx.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x ...