ASP.NET Core如何延迟加载含可注入控制器的模块程序集?
可行实现方案及问题修正
一、先解决服务注册锁定的核心问题
ASP.NET Core启动后ServiceCollection确实会被锁定,没法直接添加新服务,但有两种靠谱的解决方式:
1. 换用支持动态注册的容器(比如Autofac)
实现自定义IServiceProviderFactory,把默认服务容器替换为Autofac,这样即使容器构建完成后,也能动态注册模块内的服务:
public class DynamicAutofacFactory : IServiceProviderFactory<ContainerBuilder> { public static IAutofacServiceProvider? DynamicProvider { get; private set; } public ContainerBuilder CreateBuilder(IServiceCollection services) { var builder = new ContainerBuilder(); builder.Populate(services); return builder; } public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder) { var container = containerBuilder.Build(); DynamicProvider = new AutofacServiceProvider(container); return DynamicProvider; } } // 模块加载时注册内部服务 if (DynamicAutofacFactory.DynamicProvider != null) { var container = DynamicAutofacFactory.DynamicProvider.GetRequiredService<IContainer>(); container.ComponentRegistry.Register( RegistrationBuilder.ForType<ExampleMService>() .As<IExampleMService>() .InstancePerLifetimeScope() .CreateRegistration() ); }
2. 给模块留初始化接口+用子容器
定义IModuleInitializer接口,要求每个模块必须实现,在接口方法中完成服务注册逻辑。由于主容器已锁定,需为每个模块单独创建子容器,控制器激活时从对应子容器获取服务:
public interface IModuleInitializer { IServiceProvider InitializeModule(IServiceCollection hostServices); } // 模块内的实现示例 public class ExampleModuleInitializer : IModuleInitializer { public IServiceProvider InitializeModule(IServiceCollection hostServices) { var moduleServices = new ServiceCollection(); // 注册模块内部服务 moduleServices.AddScoped<IExampleMService, ExampleMService>(); // 继承宿主已注册的服务 moduleServices.Add(hostServices); return moduleServices.BuildServiceProvider(); } }
二、修复自定义ALC的服务实例化问题
自定义ALC的核心是每个模块单独分配一个ALC,同时将模块的服务容器与ALC绑定,确保控制器激活时能从正确的上下文获取依赖:
1. 自定义控制器激活器
实现IControllerActivator,根据控制器所在的程序集,匹配对应模块的服务容器,从容器中解析依赖:
public class ModuleControllerActivator : IControllerActivator { private readonly Dictionary<Assembly, IServiceProvider> _moduleSpCache = new(); public object Create(ControllerContext context) { var controllerAssembly = context.ActionDescriptor.ControllerTypeInfo.Assembly; if (_moduleSpCache.TryGetValue(controllerAssembly, out var sp)) { return sp.GetRequiredService(context.ActionDescriptor.ControllerTypeInfo.AsType()); } // 找不到模块容器时用宿主服务容器兜底 return context.HttpContext.RequestServices.GetRequiredService(context.ActionDescriptor.ControllerTypeInfo.AsType()); } public void Release(ControllerContext context, object controller) { if (controller is IDisposable disposable) { disposable.Dispose(); } } // 模块加载完成后,注册对应的服务容器 public void RegisterModuleSp(Assembly moduleAssembly, IServiceProvider sp) { _moduleSpCache[moduleAssembly] = sp; } }
2. ALC的卸载处理
每个模块加载到独立ALC后,需先清理控制器激活器中的缓存、释放模块服务容器,确保模块的所有对象引用被释放,再调用alc.Unload()完成模块卸载。
三、完善控制器延迟注册的触发逻辑
使用IActionDescriptorChangeProvider时,模块加载完成后必须通知框架更新路由,否则新控制器不会被识别:
public class ModuleRouteUpdater : IActionDescriptorChangeProvider { public static readonly ModuleRouteUpdater Instance = new(); private CancellationTokenSource? _tokenSource; public bool HasChanged { get; set; } public IChangeToken GetChangeToken() { _tokenSource = new CancellationTokenSource(); return new CancellationChangeToken(_tokenSource.Token); } // 模块加载完成后调用此方法,通知框架刷新路由 public void RefreshRoutes() { HasChanged = true; _tokenSource?.Cancel(); } } // 模块加载完成后执行 ModuleRouteUpdater.Instance.RefreshRoutes();
四、思路问题总结
你的核心思路是正确的,但缺少两个关键环节:
- 未解决应用启动后服务容器不可修改的问题,导致模块内部服务无法注册
- 自定义ALC未与服务容器绑定,控制器激活时无法找到模块内的服务
内容的提问来源于stack exchange,提问作者Sky
相关产品推荐
相关产品推荐

