You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 22:30:48