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

如何配置全局错误处理器以正确捕获自定义HttpResponseException?

全局异常处理配置方案

问题背景

自定义了HttpResponseException异常类:

using Newtonsoft.Json;
namespace Services.Errors.Exceptions;

public class HttpResponseException : Exception
{
    public int StatusCode { get; private set; }

    public HttpResponseException(int statusCode , string message) : base(message)
    {
        StatusCode = statusCode;
    }
}

实现了基于IExceptionFilter的错误处理器:

using System.Net;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using Services.Contracts;
using Services.Errors.Exceptions;
using JsonSerializer = System.Text.Json.JsonSerializer;

namespace Services.Errors.Handler;

public class ErrorHandlingMiddleware : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        if (context.Exception is HttpResponseException httpResponseMessage)
        {
            var error = new ErrorDto()
            {
                Code = httpResponseMessage.StatusCode,
                Description = context.Exception.Message
            };

            context.Result = new JsonResult(error) { StatusCode =  (int)httpResponseMessage.StatusCode};
        }
    }
}

并在Program.cs中注册:

builder.Services.AddControllers(options =>
{
    options.Filters.Add(typeof(ErrorHandlingMiddleware));
});

当前问题:

  • 控制器内主动抛出HttpResponseException时,能返回预期的格式化响应:
    {
      "code": 404,
      "description": "NotFound"
    }
    
  • 但路由不存在等请求触发的404异常,返回的是普通Exception的原始错误信息:
    System.Exception: Services.Errors.Exceptions.HttpResponseException: NotFound
    

问题原因

IExceptionFilter仅能捕获控制器动作执行阶段抛出的异常,而路由匹配失败、请求管道早期的异常不会经过控制器过滤器,因此无法被当前处理器捕获。

解决方案

将异常处理器改为全局中间件(Middleware),中间件可以拦截整个请求管道中的所有异常,包括管道早期的错误。

1. 修改错误处理器为中间件

using System.Net;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Services.Contracts;
using Services.Errors.Exceptions;

namespace Services.Errors.Handler;

public class ErrorHandlingMiddleware
{
    private readonly RequestDelegate _next;

    public ErrorHandlingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
            
            // 处理未触发异常的404等状态码
            if (context.Response.StatusCode == (int)HttpStatusCode.NotFound && !context.Response.HasStarted)
            {
                var error = new ErrorDto()
                {
                    Code = 404,
                    Description = "NotFound"
                };
                await WriteErrorResponse(context, error, HttpStatusCode.NotFound);
            }
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

    private async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        ErrorDto error;
        HttpStatusCode statusCode;

        if (exception is HttpResponseException httpEx)
        {
            statusCode = (HttpStatusCode)httpEx.StatusCode;
            error = new ErrorDto
            {
                Code = httpEx.StatusCode,
                Description = httpEx.Message
            };
        }
        else
        {
            // 处理其他未捕获异常,返回500
            statusCode = HttpStatusCode.InternalServerError;
            error = new ErrorDto
            {
                Code = (int)statusCode,
                Description = "服务器内部错误"
            };
        }

        context.Response.StatusCode = (int)statusCode;
        var json = JsonSerializer.Serialize(error);
        await context.Response.WriteAsync(json);
    }

    private async Task WriteErrorResponse(HttpContext context, ErrorDto error, HttpStatusCode statusCode)
    {
        context.Response.StatusCode = (int)statusCode;
        context.Response.ContentType = "application/json";
        var json = JsonSerializer.Serialize(error);
        await context.Response.WriteAsync(json);
    }
}

2. 修改注册方式

在Program.cs中替换原来的过滤器注册,改为注册中间件(需放在app.UseRouting()之后,app.UseAuthorization()和app.MapControllers()之前):

// 移除原过滤器注册代码
// builder.Services.AddControllers(options =>
// {
//     options.Filters.Add(typeof(ErrorHandlingMiddleware));
// });

// 注册全局异常处理中间件
app.UseMiddleware<ErrorHandlingMiddleware>();

app.UseAuthorization();

app.MapControllers();

3. 额外扩展

如果需要统一处理所有非200状态码(如400、401等),可在InvokeAsync方法中添加对应状态码的处理逻辑,确保所有错误都返回统一格式响应。

内容的提问来源于stack exchange,提问作者Какосян Владимир

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 02:35:00