ASP.NET Core(.NET 5)如何按路由/插件配置不同Service Container
可行实现路径
完全不需要自定义TypeInfo,通过ASP.NET Core原生的扩展点组合即可实现需求,具体步骤如下:
1. 标记插件控制器+统一路由前缀注册
- 先定义一个插件控制器标记属性,给每个插件的控制器打上对应标识,存储插件唯一ID,不需要修改类型本身的元数据
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class PluginControllerAttribute : Attribute { // 插件唯一标识,用于匹配对应容器 public string PluginId { get; set; } }
- 实现
IApplicationModelConvention扩展,扫描所有带[PluginController]标记的控制器,按插件配置统一添加路由前缀,同时把插件ID存入控制器元数据供后续激活使用
public class PluginRouteConvention : IApplicationModelConvention { private readonly IPluginConfigService _pluginConfigService; public PluginRouteConvention(IPluginConfigService pluginConfigService) { _pluginConfigService = pluginConfigService; } public void Apply(ApplicationModel application) { foreach (var controller in application.Controllers) { var pluginAttr = controller.Attributes.OfType<PluginControllerAttribute>().FirstOrDefault(); if (pluginAttr == null) continue; // 读取插件配置的路由前缀 string routePrefix = _pluginConfigService.GetRoutePrefix(pluginAttr.PluginId); // 给该插件所有控制器统一拼接前缀 controller.Selectors.ToList().ForEach(selector => { selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel( new AttributeRouteModel(new RouteAttribute(routePrefix)), selector.AttributeRouteModel); }); } } }
- 注册扩展到MVC配置
services.AddControllers(opt => { opt.Conventions.Add(new PluginRouteConvention(你的插件配置服务实例)); });
2. 自定义控制器激活器实现容器隔离
直接实现IControllerActivator替换默认激活逻辑,激活时从控制器元数据中拿到插件ID,调取对应插件的Castle Windsor容器解析实例,核心应用的控制器仍走默认激活逻辑:
public class PluginControllerActivator : IControllerActivator { private readonly IControllerActivator _defaultActivator; private readonly IPluginContainerManager _pluginContainerManager; public PluginControllerActivator(IControllerActivator defaultActivator, IPluginContainerManager pluginContainerManager) { _defaultActivator = defaultActivator; _pluginContainerManager = pluginContainerManager; } public object Create(ControllerContext context) { var controllerDesc = context.ActionDescriptor as ControllerActionDescriptor; // 非控制器请求/核心应用控制器走默认逻辑 if (controllerDesc == null) return _defaultActivator.Create(context); var pluginAttr = controllerDesc.ControllerTypeInfo.GetCustomAttribute<PluginControllerAttribute>(); if (pluginAttr == null) return _defaultActivator.Create(context); // 从插件容器管理器取对应插件的Windsor容器,解析控制器实例 var pluginContainer = _pluginContainerManager.GetContainer(pluginAttr.PluginId); return pluginContainer.Resolve(controllerDesc.ControllerTypeInfo.AsType()); } public void Release(ControllerContext context, object controller) { var controllerDesc = context.ActionDescriptor as ControllerActionDescriptor; if (controllerDesc == null) { _defaultActivator.Release(context, controller); return; } var pluginAttr = controllerDesc.ControllerTypeInfo.GetCustomAttribute<PluginControllerAttribute>(); if (pluginAttr == null) { _defaultActivator.Release(context, controller); return; } // 插件控制器由对应Windsor容器释放 var pluginContainer = _pluginContainerManager.GetContainer(pluginAttr.PluginId); pluginContainer.Release(controller); } }
替换默认的控制器激活器:
services.Replace(ServiceDescriptor.Singleton<IControllerActivator>(sp => { var defaultActivator = new DefaultControllerActivator(new TypeActivatorCache()); var containerManager = sp.GetRequiredService<IPluginContainerManager>(); return new PluginControllerActivator(defaultActivator, containerManager); }));
3. 插件容器的公共服务共享配置
你可以把核心应用的公共服务注册为每个插件Windsor容器的父容器,或者直接把公共服务实例批量注册到每个插件容器中,即可实现插件既可以调用公共服务,也可以自行注册私有服务的需求。
4. 运行时插件控制器发现(可选)
如果插件是运行时动态加载的,直接用Application Parts加载插件程序集即可,不需要自定义类型逻辑,默认的控制器发现机制可以正常识别带[PluginController]标记的控制器:
var mvcBuilder = services.AddControllers(); foreach (var pluginAssembly in 加载到的所有插件程序集集合) { mvcBuilder.AddApplicationPart(pluginAssembly); }
注意事项
- 插件控制器需要设置为
public访问级别,默认的控制器发现规则会匹配后缀为Controller的类,如果你的插件控制器命名不符合规则,可以自定义ControllerFeatureProvider修改匹配逻辑 - 如果需要除控制器之外的其他中间件、过滤器也能使用对应插件的容器,可以在中间件层匹配到插件路由前缀时,提前创建对应插件的服务作用域存入
HttpContext.Items,后续组件直接从该作用域解析服务即可
内容的提问来源于stack exchange,提问作者Jens
相关产品推荐
相关产品推荐

