Skip to content
Go back

ASP.NET Core 全局异常处理实践:从中间件到现代异常处理器

Published:  at  12:00 AM

ASP.NET Core 全局异常处理实践:从中间件到现代异常处理器

异常处理是任何后端应用架构中不可或缺的一环。对于 ASP.NET Core 开发者而言,如何设计一套高效、优雅、并能适应未来演进的全局异常处理机制,不仅关系到系统健壮性,也直接影响开发效率和 API 消费者体验。本文将系统梳理 ASP.NET Core 中主流的全局异常处理模式,结合最新特性与最佳实践,深入剖析中间件、标准化问题详情(ProblemDetails)、以及新引入的 IExceptionHandler 的使用场景与演化脉络。

从自定义中间件到标准响应格式

在 ASP.NET Core 项目中,最传统也是最常见的做法,是通过自定义中间件(Middleware)来捕获和处理未被上层捕捉的异常。这种方式结构简单,易于集成,但随着业务复杂度提升,也面临扩展性和标准化不足的问题。

自定义异常处理中间件示例:

internal sealed class GlobalExceptionHandlerMiddleware(
    RequestDelegate next,
    ILogger<GlobalExceptionHandlerMiddleware> logger)
{
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await next(context);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Unhandled exception occurred");

            // 设置状态码,并返回自定义 ProblemDetails 格式响应
            context.Response.StatusCode = ex switch
            {
                ApplicationException => StatusCodes.Status400BadRequest,
                _ => StatusCodes.Status500InternalServerError
            };

            await context.Response.WriteAsJsonAsync(
                new ProblemDetails
                {
                    Type = ex.GetType().Name,
                    Title = "An error occurred",
                    Detail = ex.Message
                });
        }
    }
}

要让该中间件生效,只需在请求管道中注册即可:

app.UseMiddleware<GlobalExceptionHandlerMiddleware>();

这种做法适用于快速原型或简单项目,但随着异常种类增多(如验证异常、资源未找到等),代码会迅速膨胀,并且自定义序列化往往与业界标准(如 RFC 9457)存在差距。

IProblemDetailsService:标准化你的错误响应

为了解决中间件方案的局限性,微软引入了 IProblemDetailsService,使开发者能够轻松生成符合标准的 ProblemDetails 响应,从而提升 API 的可用性和一致性。

升级后的中间件示例:

internal sealed class GlobalExceptionHandlerMiddleware(
    RequestDelegate next,
    IProblemDetailsService problemDetailsService,
    ILogger<GlobalExceptionHandlerMiddleware> logger)
{
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await next(context);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Unhandled exception occurred");

            context.Response.StatusCode = ex switch
            {
                ApplicationException => StatusCodes.Status400BadRequest,
                _ => StatusCodes.Status500InternalServerError
            };

            await problemDetailsService.TryWriteAsync(new ProblemDetailsContext
            {
                HttpContext = context,
                Exception = ex,
                ProblemDetails = new ProblemDetails
                {
                    Type = ex.GetType().Name,
                    Title = "An error occurred",
                    Detail = ex.Message
                }
            });
        }
    }
}

通过内置的 IProblemDetailsService,不仅减少了手动序列化的工作,还大大提升了异常响应的规范性。这一变化对于公共 API 特别重要,开发者可借助标准工具进行自动化对接和测试。

IExceptionHandler:面向未来的异常处理体系

ASP.NET Core 8 开始,官方推荐使用更具现代化、解耦性强的 IExceptionHandler 接口。它允许针对不同异常类型编写专用处理器,实现关注点分离、可测试性和灵活扩展。

自定义异常处理器的实现:

internal sealed class GlobalExceptionHandler(
    IProblemDetailsService problemDetailsService,
    ILogger<GlobalExceptionHandler> logger) : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        logger.LogError(exception, "Unhandled exception occurred");

        httpContext.Response.StatusCode = exception switch
        {
            ApplicationException => StatusCodes.Status400BadRequest,
            _ => StatusCodes.Status500InternalServerError
        };

        return await problemDetailsService.TryWriteAsync(new ProblemDetailsContext
        {
            HttpContext = httpContext,
            Exception = exception,
            ProblemDetails = new ProblemDetails
            {
                Type = exception.GetType().Name,
                Title = "An error occurred",
                Detail = exception.Message
            }
        });
    }
}

在注册时:

builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
app.UseExceptionHandler();

该机制的核心优势在于,可按需注册多个异常处理器,并通过链式尝试处理,首个能处理的处理器返回 true 即终止后续处理,实现了类似责任链模式的灵活扩展。例如,可以针对 FluentValidation 验证异常实现专用处理器:

internal sealed class ValidationExceptionHandler(
    IProblemDetailsService problemDetailsService,
    ILogger<ValidationExceptionHandler> logger) : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        if (exception is not ValidationException validationException)
            return false;

        logger.LogError(exception, "Unhandled exception occurred");

        httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        var context = new ProblemDetailsContext
        {
            HttpContext = httpContext,
            Exception = exception,
            ProblemDetails = new ProblemDetails
            {
                Detail = "One or more validation errors occurred",
                Status = StatusCodes.Status400BadRequest
            }
        };

        var errors = validationException.Errors
            .GroupBy(e => e.PropertyName)
            .ToDictionary(
                g => g.Key.ToLowerInvariant(),
                g => g.Select(e => e.ErrorMessage).ToArray()
            );
        context.ProblemDetails.Extensions.Add("errors", errors);

        return await problemDetailsService.TryWriteAsync(context);
    }
}

如此一来,验证失败时可自动返回字段级错误信息,极大提升前后端联调效率与用户体验。

实践建议与总结

回顾 ASP.NET Core 异常处理的发展历程,从最初的手写中间件,到 ProblemDetails 标准化响应,再到 IExceptionHandler 的高内聚、低耦合现代解法,每一步都在提升系统的健壮性和可维护性。建议新项目优先采用 IExceptionHandler 方案,利用其分层处理、易于测试和扩展的特点,打造一套适合自身业务的全局异常处理体系。

最重要的一点,异常处理绝不应成为事后的补丁,而应当是项目设计阶段就要关注的基础能力。早早建立统一、优雅的异常处理体系,将大幅降低生产事故带来的压力,也让系统长期演进更为顺畅。



Previous Post
深入剖析 ASP.NET Core Web API 的十大核心中间件
Next Post
在 .NET 中利用 Redis 实现高效消息通信