VS Code/Rider(Mac)下Microsoft Feature Management自定义DI注入异常与Visual Studio行为差异问题
这个问题我之前排查过类似的情况,核心原因是服务生命周期不匹配,再加上不同环境下DI容器的验证严格度差异,才导致了这种“同代码不同结果”的现象😯
问题根源分析
生命周期冲突
Microsoft Feature Management组件的AddFeatureManagement方法,内部注册的IFeatureManager是Singleton生命周期(从错误栈里的根容器解析逻辑可以佐证)。而你把自定义的DatabaseFeatureProvider注册成了Scoped,这违反了DI的核心规则:Singleton服务不能依赖Scoped服务。
因为Singleton的生命周期和应用进程一致,Scoped服务则是每次请求/范围才创建,当Singleton的IFeatureManager尝试从根容器(无Scope上下文)解析Scoped的IFeatureDefinitionProvider时,就会触发Cannot resolve scoped service from root provider的异常。环境验证差异
Windows的Visual Studio能正常运行,大概率是因为不同.NET运行时环境下,DI容器的默认验证严格度不同。Mac上的VS Code/Rider使用的.NET 9.0运行时,默认开启了更严格的DI生命周期验证,直接暴露了这个不规范的依赖问题;而Windows环境可能因为包版本兼容或运行时配置,暂时绕过了验证,但实际上这是一个隐藏的风险(可能导致Scoped服务被意外提升为Singleton,引发数据一致性问题)。
解决方案
方案1:直接调整Provider的生命周期(推荐,适用于Provider无Scoped依赖的情况)
把自定义IFeatureDefinitionProvider的注册从Scoped改成Singleton,和IFeatureManager的生命周期对齐,这是最简洁的解决方式:
修改你原有的DI注册代码:
// 原代码 // services.AddScoped<IFeatureDefinitionProvider, DatabaseFeatureProvider>().AddFeatureManagement(); // 修改后 services.AddSingleton<IFeatureDefinitionProvider, DatabaseFeatureProvider>().AddFeatureManagement();
从你提供的DatabaseFeatureProvider代码来看,它没有依赖任何Scoped服务,改成Singleton完全不会影响功能,还能解决生命周期冲突问题。
方案2:通过IServiceScopeFactory兼容Scoped依赖(如果Provider必须用Scoped服务)
如果你的DatabaseFeatureProvider需要依赖Scoped服务(比如数据库上下文DbContext),不能直接改成Singleton,那可以通过注入IServiceScopeFactory动态创建Scope来获取Scoped服务:
internal class DatabaseFeatureProvider : IFeatureDefinitionProvider { private readonly IServiceScopeFactory _scopeFactory; // 注入Scope工厂(Singleton生命周期,可安全被Singleton依赖) public DatabaseFeatureProvider(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } public async Task<FeatureDefinition?> GetFeatureDefinitionAsync(string featureName) { // 每次调用时创建独立Scope,避免Singleton持有Scoped服务 using var scope = _scopeFactory.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<YourDbContext>(); // 执行数据库查询逻辑 var feature = await dbContext.Features.FirstOrDefaultAsync(f => f.Name == featureName); if (feature == null) return null; return new FeatureDefinition { Name = feature.Name, EnabledFor = feature.IsEnabled ? new[] { new FeatureFilterConfiguration { Name = "AlwaysOn" } } : Array.Empty<FeatureFilterConfiguration>() }; } public async IAsyncEnumerable<FeatureDefinition?> GetAllFeatureDefinitionsAsync() { using var scope = _scopeFactory.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<YourDbContext>(); var features = await dbContext.Features.ToListAsync(); foreach (var feature in features) { yield return new FeatureDefinition { Name = feature.Name, EnabledFor = feature.IsEnabled ? new[] { new FeatureFilterConfiguration { Name = "AlwaysOn" } } : Array.Empty<FeatureFilterConfiguration>() }; } } }
这种情况下,DatabaseFeatureProvider依然注册为Singleton,通过IServiceScopeFactory动态创建Scope来使用Scoped服务,既避免了生命周期冲突,又能满足业务需求。
总结
优先选择方案1调整生命周期,这是最符合Feature Management组件设计的方式;如果有必须依赖Scoped服务的场景,再用方案2通过Scope工厂兼容。两种方式都能解决跨环境的DI异常问题,也能规避潜在的服务生命周期风险。




