.NET 3.1升级6.0部署AWS报无法解析根提供程序作用域IDbContext异常
问题背景
- 初始项目基于.NET Core 3.1开发,为托管在AWS平台的Web API项目
- 本地开发环境升级至.NET 6.0时,仅调整了版本升级必需的配置项,未改动任何业务逻辑代码
- 本地环境运行验证所有功能正常,部署到AWS后所有请求均触发异常
- 异常信息为
"Cannot resolve scoped service 'IDbContext' from root provider",抛出位置为负责向HttpContext.Items中添加实体的Invoke方法 - 该Invoke方法入参包含HttpContext与IDbContext,其余移除IDbContext入参的Invoke方法不再抛出异常,但当前方法必须依赖IDbContext查询机构实体并存入
HttpContext.Items - 同批次完成.NET 6升级的其他API均未出现该问题,可正常运行
相关实现代码
异常对应的InvokeAsync方法
public async Task InvokeAsync(HttpContext httpContext, IDbContext dbContext) { var identifier = GetContextIdentifier(httpContext); if (identifier != null) { string cacheKey = _key + identifier; var institution = _cache.Get(cacheKey) as Institution; if (institution == null) { institution = await dbContext.Institutions.Where(x => x.Code == identifier).FirstOrDefaultAsync(); if (institution != null) { var cacheEntryOptions = GetCacheEntryOptions(); _cache.Set(cacheKey, institution, cacheEntryOptions); } } if (institution != null) { httpContext.Items[institution.GetType().Name] = institution; } } }
项目Dockerfile配置
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY ["WebAPI.Domain/WebAPI.Domain.csproj", "WebAPI.Domain/"] COPY ["WebAPI.Application/WebAPI.Application.csproj", "WebAPI.Application/"] COPY ["WebAPI.Infrastructure/WebAPI.Infrastructure.csproj", "WebAPI.Infrastructure/"] COPY ["WebAPI.Persistence/WebAPI.Persistence.csproj", "WebAPI.Persistence/"] COPY ["WebAPI.WebApp/WebAPI.WebApp.csproj", "WebAPI.WebApp/"] RUN dotnet restore "WebAPI.WebApp/WebAPI.WebApp.csproj" COPY . . WORKDIR "/src/WebAPI.WebApp" RUN dotnet build "WebAPI.WebApp.csproj" -c Release -o /app FROM build AS publish RUN dotnet publish "WebAPI.WebApp.csproj" -c Release -o /app FROM base AS final WORKDIR /app COPY --from=publish /app . ENTRYPOINT ["dotnet", "WebAPI.WebApp.dll"]
Startup服务与中间件管道配置
namespace WebAPI.WebApp { public class Startup { public Startup(IConfiguration configuration, IWebHostEnvironment environment) { Configuration = configuration; Environment = environment; } public IConfiguration Configuration { get; } public IWebHostEnvironment Environment { get; } readonly string DevelopmentOrigins = "_developmentOrigins"; public void ConfigureServices(IServiceCollection services) { services.AddMultitenancy<Tenant, TenantResolver>(); services.AddDbContext<IDbContext, DbContext>(opt => opt.UseNpgsql(Configuration.GetConnectionString("DatabaseWrite"))); services.AddDbContext<ITenantDbContext, TenantDbContext>(opt => opt.UseNpgsql(Configuration.GetConnectionString("DatabaseWrite"))); services.AddCors(options => { options.AddDefaultPolicy(builder => { builder.AllowAnyMethod(); builder.AllowAnyHeader(); builder.WithOrigins("https://*.abc.education", "https://abc.education").SetIsOriginAllowedToAllowWildcardSubdomains(); }); options.AddPolicy(DevelopmentOrigins, builder => { builder.AllowAnyMethod(); builder.AllowAnyHeader(); builder.AllowAnyOrigin(); }); }); services.Configure<AppSettings>((settings) => { Configuration.GetSection("AppSettings").Bind(settings); }); services.Configure<AwsOptions>((settings) => { Configuration.GetSection("Aws").Bind(settings); }); services.AddSingleton<AuthFilter>(); services.AddScoped<BasicAuthorizationFilter>(); services .AddControllersWithViews() .AddNewtonsoftJson(); services.AddMvc() .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<IDbContext>()); services.AddApplication(); services.AddInfrastructure(); services.AddControllers(); services.AddDefaultAWSOptions(Configuration.GetAWSOptions()); services.AddAWSService<IAmazonS3>(); services.AddAWSService<IAmazonDynamoDB>(); services.AddTransient<IDynamoDBContext, DynamoDBContext>(ctx => { var client = ctx.GetService<IAmazonDynamoDB>(); var options = ctx.GetService<IOptions<AwsOptions>>(); return new DynamoDBContext(client, new DynamoDBContextConfig { TableNamePrefix = options.Value.DynamoDBTableNamePrefix, Conversion = DynamoDBEntryConversion.V2 }); }); services.AddScoped<AwsSqsService>(); services.AddScoped<AwsCloudFrontService>(); services.AddScoped<FileService>(); services .AddHttpClient<IApplicationService, ApplicationService>() .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { AllowAutoRedirect = false }); services .AddHttpClient<IInstitutionService, InstitutionService>() .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { AllowAutoRedirect = false }); services.AddScoped<IUsernameResolver, UsernameResolver>(); services.AddScoped<IRoleResolver, RoleResolver>(); services.AddScoped<IInstitutionResolver, InstitutionResolver>(); services.AddScoped<IDatabaseResolver, DatabaseResolver>(); services.AddSingleton(Environment.ContentRootFileProvider); services .AddHealthChecks() .AddNpgSql(Configuration["ConnectionStrings:DatabaseWrite"]); } private static void HandleWithoutTenant(IApplicationBuilder app) { app.UseDatabase(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseHealthChecks("/v1/webapi/health"); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/error"); app.UseHsts(); } app.UseCustomExceptionHandler(); app.UseRouting(); if (env.IsEnvironment("Local")) app.UseCors(DevelopmentOrigins); else app.UseCors(); app.UseAuthorization(); app.UseWhen(context => ShouldUseTenant(context), HandleWithoutTenant); app.UseWhen( context => !(context.Request.Method.Equals(HttpMethod.Options.Method, StringComparison.OrdinalIgnoreCase)) && !(ShouldUseTenant(context)), appBuilder => { appBuilder.UseMultitenancy<Tenant>(); appBuilder.UseDatabase(); appBuilder.UseInstitutionResolution(); } ); app.UseHttpsRedirection(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } }
故障原因
.NET 6生产环境默认开启严格的依赖注入作用域校验,和.NET Core 3.1的校验规则存在差异:
- 本地开发环境校验规则宽松,允许部分场景下从根容器解析Scoped生命周期服务做兼容,因此本地运行无异常
- AWS部署时运行在生产环境,严格禁止从根服务提供者解析Scoped类型服务。机构解析中间件被注册为单例生命周期,中间件实例从根容器创建时,框架尝试从根提供者解析Invoke方法依赖的
IDbContext(Scoped生命周期),直接抛出异常。其余中间件未注入Scoped类型参数,因此不会触发该问题。 - 额外发现贴出的InvokeAsync方法缺失
await _next(httpContext)调用,会导致中间件管道截断,请求无法流转到后续逻辑。
修复方案
- 检查
UseInstitutionResolution扩展方法的实现,不要手动将对应中间件注册为单例,使用标准UseMiddleware<T>()方式注册,让框架按请求作用域创建中间件实例。 - 最稳妥的Scoped服务引用方式是从当前请求的作用域容器解析,不要直接通过Invoke方法参数注入,修改后的InvokeAsync实现如下:
public async Task InvokeAsync(HttpContext httpContext) { // 从当前请求作用域解析IDbContext,规避根容器解析异常 var dbContext = httpContext.RequestServices.GetRequiredService<IDbContext>(); var identifier = GetContextIdentifier(httpContext); if (identifier != null) { string cacheKey = _key + identifier; var institution = _cache.Get(cacheKey) as Institution; if (institution == null) { institution = await dbContext.Institutions.Where(x => x.Code == identifier).FirstOrDefaultAsync(); if (institution != null) { var cacheEntryOptions = GetCacheEntryOptions(); _cache.Set(cacheKey, institution, cacheEntryOptions); } } if (institution != null) { httpContext.Items[institution.GetType().Name] = institution; } } // 补全管道流转调用 await _next(httpContext); }
- 重新构建镜像部署到AWS后即可恢复正常。
内容的提问来源于stack exchange,提问作者Otavio Rockenbach
相关产品推荐
相关产品推荐

