1. 程式人生 > >.net core3.1 abp動態選單和動態許可權(動態選單實現和動態許可權新增) (三)

.net core3.1 abp動態選單和動態許可權(動態選單實現和動態許可權新增) (三)

我們來建立動態選單吧 

首先,先對動態選單的概念、操作、流程進行約束:
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 = 0)
        {
            var loginTenant = id <= 0 ? 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("0","Host管理員") { IsSelected = id == 0 });

            viewModel.TeneacyItems.Add(new ComboboxItemDto("-1", "預設選單") { IsSelected = id == -1 });

            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="2">
                        </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>
View Code

 

修改_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="64" minlength="2">
            </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="128">
            </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="128">
            </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="128">
            </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="128">
            </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="128">
            </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>
View Code

修改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 == 0 ? 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 > 0)
            {
                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 == -1);

                        var defaultMenus = await AsyncQueryableExecuter.ToListAsync(query);
                        if (defaultMenus.Any())
                        {
                            List<MenusInsert> GetMenusInserts(List<AbpMenus> abpMenus,int parentId = 0)
                            {
                                List<MenusInsert> menusInserts = new List<MenusInsert>();
                                foreach (var entity in abpMenus.Where(o => o.ParentId == parentId))
                                {
                                    var insert = new MenusInsert()
                                    {
                                        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
                                    };
                                    insert.menusInserts = GetMenusInserts(abpMenus, entity.Id);
                                    menusInserts.Add(insert);
                                }
                                return menusInserts;
                            }

                            async Task InsertMenusAsync(List<MenusInsert> inserts,int parentId = 0)
                            {
                                foreach (var insert in inserts)
                                {
                                    var entity = await CreateAsync(new AbpMenus()
                                    {
                                        LName = insert.LName,
                                        MenuName = insert.MenuName,
                                        PageName = insert.PageName,
                                        Icon = insert.Icon,
                                        Url = insert.Url,
                                        IsActive = insert.IsActive,
                                        Orders = insert.Orders,
                                        ParentId = parentId,
                                        TenantId = tenant.Id
                                    });
                                    if (insert.menusInserts.Any())
                                    {
                                        await InsertMenusAsync(insert.menusInserts, entity.Id);
                                    }
                                }
                            }
                            await InsertMenusAsync(GetMenusInserts(defaultMenus));
                            
                        }
                    }
                }
            }

        }
        #endregion


        #region 賦予當前租戶Admin角色選單許可權
        /// <summary>
        /// 賦予當前租戶Admin角色選單許可權
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task GivePermissionsAsync(EntityDto<int> input)
        {
            if (input.Id > 0)
            {
                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 = 10;
                        var active_permissions = ConvertTenantPermissions(systemMenus.Where(o => o.IsActive).ToList());
                        for (int i = 0; i < active_permissions.Count(); i += 10)//每次後移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 += 10;//每次從陣列中選出N+10位,skip前N位
                        }

                        var notActive_BatchCount = 10;
                        var notActive_permissions = ConvertTenantPermissions(systemMenus.Where(o => !o.IsActive).ToList());
                        for (int i = 0; i < notActive_permissions.Count(); i += 10)//每次後移5位
                        {
                            foreach (var notActive_permission in notActive_permissions.Take(notActive_BatchCount).Skip(i))
                            {
                                await _roleManager.ProhibitPermissionAsync(adminRole, notActive_permission);
                            }
                            notActive_BatchCount += 10;//每次從陣列中選出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 == 0);

                    var systemMenus = await AsyncQueryableExecuter.ToListAsync(query);

                    //await _roleManager.ResetAllPermissionsAsync(adminRole.FirstOrDefault()); //重置授權

                    var active_BatchCount = 10;
                    var active_permissions = ConvertHostPermissions(systemMenus.Where(o => o.IsActive).ToList());
                    for (int i = 0; i < active_permissions.Count(); i += 10)//每次後移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 += 10;//每次從陣列中選出N+10位,skip前N位
                    }

                    var notActive_BatchCount = 10;
                    var notActive_permissions = ConvertHostPermissions(systemMenus.Where(o => !o.IsActive).ToList());
                    for (int i = 0; i < notActive_permissions.Count(); i += 10)//每次後移5位
                    {
                        foreach (var notActive_permission in notActive_permissions.Take(notActive_BatchCount).Skip(i))
                        {
                            await _roleManager.ProhibitPermissionAsync(adminRole, notActive_permission);
                        }
                        notActive_BatchCount += 10;//每次從陣列中選出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 == 0).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 > 0)
            {
                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 = 0;

            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)) > 0)
                    {
                        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", 0);
            childJObject.Add("text", "根目錄");
            childJObject.Add("icon", "");
            childJObject.Add("state", openJObject);
            childJObject.Add("children", GetJArray(systemMenus, 0));
            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

下一篇開始動態許可權

&n