You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

VS Code/Rider(Mac)下Microsoft Feature Management自定义DI注入异常与Visual Studio行为差异问题

VS Code/Rider(Mac)下Microsoft Feature Management自定义DI注入异常与Visual Studio行为差异问题

这个问题我之前排查过类似的情况,核心原因是服务生命周期不匹配,再加上不同环境下DI容器的验证严格度差异,才导致了这种“同代码不同结果”的现象😯

问题根源分析

  1. 生命周期冲突
    Microsoft Feature Management组件的AddFeatureManagement方法,内部注册的IFeatureManagerSingleton生命周期(从错误栈里的根容器解析逻辑可以佐证)。而你把自定义的DatabaseFeatureProvider注册成了Scoped,这违反了DI的核心规则:Singleton服务不能依赖Scoped服务
    因为Singleton的生命周期和应用进程一致,Scoped服务则是每次请求/范围才创建,当Singleton的IFeatureManager尝试从根容器(无Scope上下文)解析Scoped的IFeatureDefinitionProvider时,就会触发Cannot resolve scoped service from root provider的异常。

  2. 环境验证差异
    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异常问题,也能规避潜在的服务生命周期风险。

火山引擎 最新活动