.NET 8中如何创建依赖其他服务并传入IServiceProvider的单例服务
问题解决:.NET 8中依赖其他服务的单例服务注册方案
错误原因分析
你遇到的System.InvalidOperationException: The service collection cannot be modified because it is read-only异常,是因为在WebApplication构建完成(调用builder.Build())后,服务容器已经固化为只读状态,此时无法再向builder.Services添加新服务。
正确实现方案
核心思路是在服务注册阶段(容器未固化时),利用DI的工厂委托延迟实例化服务,让容器自动提供IServiceProvider来获取依赖,而非手动提前传入。
1. 重构GetAppLogger扩展方法
修改为接收Func<IServiceProvider, T>的重载,让DI容器在需要实例化时自动注入IServiceProvider:
public static IServiceCollection GetAppLogger(this IServiceCollection services) { services.AddSingleton<IAppLogger<Badboy.Models.LoggingEvent>>(serviceProvider => new LoggerAdapter<Badboy.Models.LoggingEvent>( serviceProvider.GetRequiredService<ILoggerFactory>(), serviceProvider.GetRequiredService<IProducerService>())); return services; }
- 使用
GetRequiredService替代GetService,确保依赖服务一定存在,避免空引用异常。 - 无需手动传入
IServiceProvider,委托会由DI容器自动触发。
2. 调整Program.cs中的调用逻辑
直接链式调用扩展方法即可,不需要额外传入参数:
var builder = WebApplication.CreateBuilder(args); // 注册基础服务 builder.Services.SetupLoggerFactory(); builder.Services.SetupKafkaProducerForFoo(builder.Configuration); // 注册依赖其他服务的单例 builder.Services.GetAppLogger(); // 构建应用(此时服务容器才会固化) var app = builder.Build(); // 后续中间件配置、启动逻辑...
3. 可选:优化其他服务的注册方式
为了保持一致性,你也可以将另外两个服务的注册改为工厂委托形式(当前写法功能正常,但更符合DI规范):
public static IServiceCollection SetupLoggerFactory(this IServiceCollection services) { services.AddSingleton<ILoggerFactory>(_ => new LoggerFactory()); return services; } public static IServiceCollection SetupKafkaProducerForFoo(this IServiceCollection services, IConfiguration config) { // 如果ProducerService需要IConfiguration,可通过委托传递 services.AddSingleton<IProducerService>(_ => new ProducerService(config)); return services; }
方案原理
通过工厂委托,DI容器会在首次请求IAppLogger<LoggingEvent>实例时才执行委托内的逻辑,此时IServiceProvider已经完全构建完成,所有依赖的服务(ILoggerFactory、IProducerService)都已注册并可正常获取,同时避免了在容器固化后修改服务集合的问题。
内容的提问来源于stack exchange,提问作者Adi Prasetyo
相关产品推荐
相关产品推荐

