ABP框架中的Navigation功能用于管理业务系统中所有可用的菜单导航控件,通常在业务系统的首页会有一个全局性的导航菜单,JD商城,天猫,猪八戒网莫不如是。所以为方便起见,Navigation功能默认定义了一个"MainMenu"菜单添加到缓存字典中。该Navigation功能与普通ERP项目中可定制动态生成的导航菜单最大的区别应该是每一个菜单定义(MenuItemDefinition)可以设置一个权限只有用户拥有权限才会显示给该用户,控制更加的细更加的松耦合不是直接绑定到某个用户上。
整体而言,Navigation分为两部分,第一部分是菜单定义部分,业务系统中所有可用菜单项的定义都会存储其中,第二部分是用户导向,是面向具体用户返回具体菜单信息,就是根据用户自身的权限过滤出一部分菜单定义项(MenuItemDefinition)并转换成UserMenuItem返回给用户。
1.菜单定义
菜单定义主要是MenuDefinition和MenuItemDefinition两类。前者代表一个菜单,会拥有一个MenuItemDefinition的集合表示所拥有的菜单选项,每一个MenuItemDefinition自身还会拥有子MenuItemDefinition的集合。
业务项目开发者定义一个菜单的操作与上篇Feature的定义方式基本相同,定义一个集成自NavigationProvider的子类重写SetNavigation方法,在方法体中通过引用到的单例INavigationManager,注册进INavigationManager所拥有的一个缓存字典中,具体实例如下:
public class MyNavigationProvider1 : NavigationProvider { public override void SetNavigation(INavigationProviderContext context) { context.Manager.MainMenu.AddItem( new MenuItemDefinition( "Abp.Zero.Administration", new FixedLocalizableString("Administration"), "fa fa-asterisk", requiresAuthentication: true ).AddItem( new MenuItemDefinition( "Abp.Zero.Administration.User", new FixedLocalizableString("User management"), "fa fa-users", "#/admin/users", requiredPermissionName: "Abp.Zero.UserManagement", customData: "A simple test data" ) ).AddItem( new MenuItemDefinition( "Abp.Zero.Administration.Role", new FixedLocalizableString("Role management"), "fa fa-star-o", "#/admin/roles", requiredPermissionName: "Abp.Zero.RoleManagement" ) ) ); } }
在自定义的AbpModule中通过NavigationConfiguration注册到Providers属性中,而NavigationManager的Initialize方法会获取NavigationConfiguration的Providers逐个调用SetNavigation,完成整个菜单导航的注册。
2.用户菜单
真正会被用作菜单数据的用户菜单也有UserMenu和UserMenuItem两个类和上面的MenuDefinition和MenuItemDefinition是对应的,单例的UserNavigationManager提供了两个方法GetMenuAsync,GetMenusAsync,分别可以根据菜单导航名返回导航数据和根据用户Id,返回该用户所拥有的所有导航菜单。
在GetMenuAsync,GetMenusAsync中会先获取全部的菜单定义项,根据定义项中是否拥有权限控制,有的话就制返回满足权限的菜单项。具体的逻辑如下:
private async Task<int> FillUserMenuItems(int? tenantId, long? userId, IList<MenuItemDefinition> menuItemDefinitions, IList<UserMenuItem> userMenuItems) { var addedMenuItemCount = 0; using (var featureDependencyContext = _iocResolver.ResolveAsDisposable<FeatureDependencyContext>()) { featureDependencyContext.Object.TenantId = tenantId; foreach (var menuItemDefinition in menuItemDefinitions) { if (menuItemDefinition.RequiresAuthentication && !userId.HasValue) { continue; } if (!string.IsNullOrEmpty(menuItemDefinition.RequiredPermissionName) && (!userId.HasValue || !(await PermissionChecker.IsGrantedAsync(userId.Value, menuItemDefinition.RequiredPermissionName)))) { continue; } if (menuItemDefinition.FeatureDependency != null && (AbpSession.MultiTenancySide == MultiTenancySides.Tenant || tenantId.HasValue) && !(await menuItemDefinition.FeatureDependency.IsSatisfiedAsync(featureDependencyContext.Object))) { continue; } var userMenuItem = new UserMenuItem(menuItemDefinition, _localizationContext); if (menuItemDefinition.IsLeaf || (await FillUserMenuItems(tenantId, userId, menuItemDefinition.Items, userMenuItem.Items)) > 0) { userMenuItems.Add(userMenuItem); ++addedMenuItemCount; } } } return addedMenuItemCount; }
用户菜单就是这么简单。