You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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容器的这个设计是为了避免重复执行大概率会失败的服务解析逻辑——如果第一次创建失败是由于注册配置错误(比如依赖缺失)或构造函数本身的硬编码错误,后续重复尝试只会浪费资源。因此容器会将解析失败的结果缓存,后续请求直接复用这个异常。

解决思路

  1. 排查构造函数异常根源:先定位ITenantValidator实现类构造函数抛出异常的具体原因,比如是否依赖了未正确注册的服务、初始化时访问了不可用的外部资源,或是存在逻辑错误。
  2. 将初始化逻辑移出构造函数:构造函数仅做依赖注入,把可能失败的初始化逻辑放到单独的方法(比如InitializeAsync)中,在中间件的Invoke方法里显式调用并处理异常。这样即使某次初始化失败,后续请求仍能重新尝试创建实例并执行初始化。
  3. 避免DI容器缓存失败状态:如果必须在构造函数中处理逻辑,唯一可行的方式是修复构造函数的异常问题,确保实例能正常创建。.NET Core DI容器没有公开的缓存清除接口,无法手动重置失败状态。

额外注意:中间件注册顺序

你的HeaderValidator中间件注册在UseHealthChecks之前,意味着健康检查请求也会经过该中间件。虽然中间件里已经处理了/health路径的逻辑,但如果健康检查请求触发了ITenantValidator的创建,首次健康检查的构造异常同样会导致后续所有请求异常。


内容的提问来源于stack exchange,提问作者aditya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 07:12:52