.NET Core自定义中间件异常求助:首次报错后后续请求复用相同异常
问题分析:自定义中间件中Scoped类构造异常导致后续请求重复返回相同异常的原因
在.NET Core中使用自定义中间件验证API请求头,中间件依赖一个Scoped生命周期的ITenantValidator实现类。首次请求时如果该类的构造函数抛出异常,后续请求的代码无法进入该类,但仍会返回相同的异常,以下是原因分析:
中间件代码
public class HeaderValidator { private readonly RequestDelegate _next; public HeaderValidator(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context, ITenantValidator tenantValidator) { try { if (!context.Request.Headers.ContainsKey(Constant.s_tenant_header)) { context.Request.Headers.Add(Constant.s_tenant_header, TenantId.US.ToString()); } //允许'/health'端点调用不带请求头,其他请求若未传入x-tenant则拒绝 if (!context.Request.Path.Equals("/health") && !context.Request.Headers.ContainsKey(Constant.s_tenant_header)) { context.Response.StatusCode = 400; await context.Response.WriteAsync("'x-tenant'必须在请求头中提供"); } //允许'/health'端点调用不带请求头,若传入的x-tenant不支持则拒绝请求 else if (!context.Request.Path.Equals("/health") && !tenantValidator.IsValidTenant(context?.Request?.Headers[Constant.s_tenant_header])) { context.Response.StatusCode = 404; await context.Response.WriteAsync($"提供的'x-tenant'不被支持"); } else await _next.Invoke(context); } catch (Exception e) { context.Response.StatusCode = (int)HttpStatusCode.BadRequest; await context.Response.WriteAsync($"处理过程中发生异常"); } } }
服务注册代码
services.AddScoped<IChannelToken, ChannelToken>();
注:此处注册的是
IChannelToken,但中间件依赖的是ITenantValidator,推测可能是笔误,实际应为对应ITenantValidator实现类的注册。
中间件注册代码
app.UseMiddleware<HeaderValidator>(); app.UseHealthChecks("/health"); app.UseStatusCodePages(); app.UseAuthentication(); app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
核心原因:DI容器的失败状态缓存机制
当首次请求触发ITenantValidator实例创建时,其构造函数抛出异常,.NET Core的DI容器会缓存这个服务解析失败的状态。后续请求再尝试获取该Scoped服务时,容器不会重新执行实例创建逻辑,而是直接返回之前缓存的异常,导致所有后续请求都返回相同的错误,且无法进入该类的业务代码。
为什么会缓存失败状态?
DI容器的这个设计是为了避免重复执行大概率会失败的服务解析逻辑——如果第一次创建失败是由于注册配置错误(比如依赖缺失)或构造函数本身的硬编码错误,后续重复尝试只会浪费资源。因此容器会将解析失败的结果缓存,后续请求直接复用这个异常。
解决思路
- 排查构造函数异常根源:先定位
ITenantValidator实现类构造函数抛出异常的具体原因,比如是否依赖了未正确注册的服务、初始化时访问了不可用的外部资源,或是存在逻辑错误。 - 将初始化逻辑移出构造函数:构造函数仅做依赖注入,把可能失败的初始化逻辑放到单独的方法(比如
InitializeAsync)中,在中间件的Invoke方法里显式调用并处理异常。这样即使某次初始化失败,后续请求仍能重新尝试创建实例并执行初始化。 - 避免DI容器缓存失败状态:如果必须在构造函数中处理逻辑,唯一可行的方式是修复构造函数的异常问题,确保实例能正常创建。.NET Core DI容器没有公开的缓存清除接口,无法手动重置失败状态。
额外注意:中间件注册顺序
你的HeaderValidator中间件注册在UseHealthChecks之前,意味着健康检查请求也会经过该中间件。虽然中间件里已经处理了/health路径的逻辑,但如果健康检查请求触发了ITenantValidator的创建,首次健康检查的构造异常同样会导致后续所有请求异常。
内容的提问来源于stack exchange,提问作者aditya
相关产品推荐
相关产品推荐

