ASP.NET Core 6运行时注册DI组件遇异常,求解决方案
解决ASP.NET Core运行时动态注册DbContext和仓储的问题
你遇到的Cannot modify ServiceCollection after application is built.异常,核心原因是ASP.NET Core在应用启动完成(执行builder.Build()生成ServiceProvider后),原始的ServiceCollection会被锁定,无法再添加或修改服务注册。直接通过保存的ServiceCollection实例去新增服务,必然触发这个错误。
下面是几种可行的解决方案:
方案一:使用动态服务工厂模式
不直接将DbContext和仓储注册到原生DI容器,而是自定义一个工厂类,负责在运行时加载程序集后创建对应的服务实例。控制器依赖这个工厂,而非具体的服务类型。
实现步骤:
- 定义服务工厂接口:
public interface IDynamicServiceFactory { T GetService<T>() where T : class; object GetService(Type serviceType); void RegisterService(Type serviceType, Func<object> factory); }
- 实现工厂类(单例注册):
public class DynamicServiceFactory : IDynamicServiceFactory { private readonly ConcurrentDictionary<Type, Func<object>> _serviceFactories = new(); private readonly IServiceProvider _rootProvider; public DynamicServiceFactory(IServiceProvider rootProvider) { _rootProvider = rootProvider; } public T GetService<T>() where T : class { return (T)GetService(typeof(T)); } public object GetService(Type serviceType) { if (_serviceFactories.TryGetValue(serviceType, out var factory)) { return factory(); } // fallback到原生DI return _rootProvider.GetService(serviceType); } public void RegisterService(Type serviceType, Func<object> factory) { _serviceFactories.AddOrUpdate(serviceType, factory, (_, __) => factory); } }
- 启动时注册工厂:
builder.Services.AddSingleton<IDynamicServiceFactory, DynamicServiceFactory>();
- 修改你的Install接口,通过工厂注册服务:
[HttpGet("install")] public async Task<bool> Install(IDynamicServiceFactory factory) { var assembly = ... // 加载外部程序集 var implementationTypes = assembly.GetTypes() .Where(x => !x.IsInterface && !x.IsAbstract && typeof(IService).IsAssignableFrom(x)); foreach (var implementationType in implementationTypes) { // 注册创建实例的委托,处理构造函数依赖(从原生DI获取) factory.RegisterService(implementationType, () => { var ctor = implementationType.GetConstructors().First(); var parameters = ctor.GetParameters() .Select(p => _rootProvider.GetService(p.ParameterType)) .ToArray(); return Activator.CreateInstance(implementationType, parameters); }); } // 注册控制器(你已实现的逻辑) ... return true; }
- 控制器中通过工厂获取服务:
public class MyController : ControllerBase { private readonly IDynamicServiceFactory _factory; public MyController(IDynamicServiceFactory factory) { _factory = factory; } [HttpGet] public IActionResult Get() { var myRepository = _factory.GetService<IMyRepository>(); // 使用仓储逻辑 return Ok(); } }
方案二:替换为支持运行时更新的DI容器
比如使用Autofac作为ASP.NET Core的DI容器,它支持运行时更新容器注册。
实现步骤:
- 安装Autofac相关包:
Install-Package Autofac Install-Package Autofac.Extensions.DependencyInjection
- 启动时配置Autofac:
var builder = WebApplication.CreateBuilder(args); // 替换默认DI为Autofac builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => { // 注册现有基础服务 containerBuilder.RegisterType<SomeService>().As<ISomeService>(); });
- 保存Autofac容器实例用于运行时更新:
public class AutofacContainerProvider { public IContainer Container { get; } public AutofacContainerProvider(IContainer container) { Container = container; } } // 在应用启动完成后注册 var app = builder.Build(); app.Services.AddSingleton(new AutofacContainerProvider(app.Services.GetRequiredService<IContainer>()));
- 在Install接口中更新Autofac容器:
[HttpGet("install")] public async Task<bool> Install(AutofacContainerProvider containerProvider) { var assembly = ... // 加载外部程序集 var implementationTypes = assembly.GetTypes() .Where(x => !x.IsInterface && !x.IsAbstract && typeof(IService).IsAssignableFrom(x)); // 基于现有容器创建新构建器 var builder = new ContainerBuilder(); foreach (var implementationType in implementationTypes) { builder.RegisterType(implementationType).AsSelf().InstancePerLifetimeScope(); // 注册服务对应的接口(排除标记接口IService) var interfaces = implementationType.GetInterfaces().Where(i => i != typeof(IService)); foreach (var iface in interfaces) { builder.RegisterType(implementationType).As(iface).InstancePerLifetimeScope(); } } // 更新现有容器 builder.Update(containerProvider.Container); // 注册控制器(你已实现的逻辑) ... return true; }
注意事项
- 运行时注册服务要考虑线程安全,多请求调用Install接口时需加锁处理。
- DbContext注册要确保能正确获取连接字符串等配置信息。
- Scoped服务使用工厂模式时,要注意实例生命周期管理,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Santiago
相关产品推荐
相关产品推荐

