ABP 5.14 NavigationProvider中注入服务实现动态菜单报错如何解决?
报错原因说明
你遇到的两个异常都是ABP启动阶段的服务生命周期特性导致的:
- 注入应用服务时抛出
AutoMapperObjectMapper依赖无法满足的异常:NavigationProvider初始化阶段,AutoMapper配置还未完成构建,依赖AutoMapper的应用服务无法被正常解析。 - 调用Manager层方法时抛出DbContext已释放的异常:启动阶段解析得到的Manager依赖的DbContext,会在启动流程对应Scope结束后被自动销毁,后续访问数据库时对应的DbContext实例已不存在。
解决方案1:手动创建Scope解析服务(启动时一次性生成菜单)
不需要修改现有业务逻辑,仅调整NavigationProvider的服务注入和调用方式即可解决问题:
- 构造函数仅注入
IServiceScopeFactory,不直接注入应用服务或Manager服务 - 在
SetNavigation方法内手动创建独立的生命周期Scope,从Scope中解析所需服务 - 异步数据库操作使用ABP封装的
AsyncHelper.RunSync执行,避免异步同步混用死锁
参考代码:
public class CustomNavigationProvider : NavigationProvider { private readonly IServiceScopeFactory _serviceScopeFactory; // 仅注入Scope工厂 public CustomNavigationProvider(IServiceScopeFactory serviceScopeFactory) { _serviceScopeFactory = serviceScopeFactory; } public override void SetNavigation(INavigationProviderContext context) { // 创建独立Scope,保证内部服务生命周期受控 using var scope = _serviceScopeFactory.CreateScope(); // 解析需要的服务 var menuManager = scope.ServiceProvider.GetRequiredService<IMenuManager>(); // 执行数据库查询,异步方法同步调用 var dynamicMenuList = AsyncHelper.RunSync(() => menuManager.GetAllMenusFromDbAsync()); // 把动态菜单添加到导航配置中 foreach (var menu in dynamicMenuList) { context.Manager.MainMenu.AddItem( new MenuItemDefinition( name: menu.Code, displayName: new FixedLocalizableString(menu.Name), url: menu.Route, icon: menu.Icon, requiredPermissionName: menu.Permission ) ); } } }
该方案的菜单仅在应用启动时生成一次,数据库菜单配置变更后需要重启应用生效。
解决方案2:渲染阶段动态拼接菜单(支持实时更新、按用户权限动态展示)
如果需要菜单支持实时更新、不同用户看到不同菜单,推荐避开NavigationProvider的启动阶段限制,在菜单返回给前端的环节动态拼接:
- 仅在NavigationProvider中配置固定不变的静态菜单
- 在菜单查询接口/视图组件中,注入
INavigationManager获取静态菜单配置,同时注入Manager/应用服务查询动态菜单 - 合并静态、动态菜单后再返回给前端渲染
该方案完全避开了启动阶段的服务依赖限制,菜单配置变更后无需重启应用即可生效,还支持按当前登录用户权限动态过滤菜单。
注意事项
- 不要直接从根依赖容器解析服务,必须通过
IServiceScopeFactory创建独立Scope后解析,避免出现内存泄漏、DbContext生命周期异常问题 - 启动阶段的同步上下文下,异步方法不要直接使用
.Result或.Wait()调用,优先使用ABP封装的AsyncHelper.RunSync执行,避免死锁
内容的提问来源于stack exchange,提问作者Firas
相关产品推荐
相关产品推荐

