处理 ASP.NET 核心 API 中的错误

注释

此版本不是本文的最新版本。 要查看当前版本,请参阅本文的.NET 9 版本

警告

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 要查看当前版本,请参阅本文的.NET 9 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft对此处提供的信息不作任何明示或暗示的保证。

要查看当前版本,请参阅本文的.NET 9 版本

本文介绍如何处理 ASP.NET 核心 API 中的错误。 已选择“最小 API”文档。 若要查看基于控制器的 API 的文档,请选择 “控制器 ”选项卡。有关 Blazor 错误处理指南,请参阅 在 ASP.NET Core Blazor 应用中处理错误

开发人员异常页

开发人员异常页显示有关未经处理的请求异常的详细信息。 它用于 DeveloperExceptionPageMiddleware 从 HTTP 管道捕获同步和异步异常,并生成错误响应。 开发人员异常页在中间件管道中提前运行,以便它可以捕获在后面的中间件中引发的未经处理的异常。

ASP.NET 核心应用在以下两者中默认启用开发人员例外页:

使用早期模板创建的应用(即通过使用 WebHost.CreateDefaultBuilder)可以通过调用 app.UseDeveloperExceptionPage来启用开发人员例外页。

警告

除非 应用在开发环境中运行,否则不要启用开发人员异常页。 当应用在生产环境中运行时,请勿公开共享详细的异常信息。 有关配置环境的详细信息,请参阅 ASP.NET Core 运行时环境

开发人员异常页可以包括有关异常和请求的以下信息:

  • 堆栈跟踪
  • 查询字符串参数(如果有)
  • Cookie(如果有)
  • Headers
  • 终结点元数据(如果有)

不保证开发人员例外页提供任何信息。 使用 日志记录 获取完整的错误信息。

下图显示了一个示例开发人员异常页,其中包含用于显示选项卡和显示的信息的动画:

开发人员异常页已动画显示所选的每个选项卡。

为了响应具有 Accept: text/plain 标头的请求,开发人员异常页返回纯文本而不是 HTML。 例如:

Status: 500 Internal Server Error
Time: 9.39 msSize: 480 bytes
FormattedRawHeadersRequest
Body
text/plain; charset=utf-8, 480 bytes
System.InvalidOperationException: Sample Exception
   at WebApplicationMinimal.Program.<>c.<Main>b__0_0() in C:\Source\WebApplicationMinimal\Program.cs:line 12
   at lambda_method1(Closure, Object, HttpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

HEADERS
=======
Accept: text/plain
Host: localhost:7267
traceparent: 00-0eab195ea19d07b90a46cd7d6bf2f

若要在最小 API 中查看开发人员异常页,请执行以下作:

本部分介绍以下示例应用,演示在最小 API 中处理异常的方法。 当请求终结点 /exception 时,它会引发异常:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/exception", () => 
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

异常处理程序

在非开发环境中,使用 异常处理程序中间件 生成错误有效负载。

若要配置 Exception Handler Middleware,请调用 UseExceptionHandler。 例如,以下代码更改应用以响应 符合 RFC 7807 的有效负载到客户端。 有关详细信息,请参阅本文后面的 “问题详细信息 ”部分。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp 
    => exceptionHandlerApp.Run(async context 
        => await Results.Problem()
                     .ExecuteAsync(context)));

app.MapGet("/exception", () => 
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

客户端和服务器错误响应

请考虑以下最小 API 应用。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)));

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

终结点/users生成的200 OKjson表示形式User为何时id大于0,否则400 BAD REQUEST为无响应正文的状态代码。 有关创建响应的详细信息,请参阅 “在最小 API 应用中创建响应”。

Status Code Pages middleware可以配置为为所有 HTTP 客户端()或服务器(400-)响应生成公共正文内容(499500 -599如果为空)。 中间件是通过调用 UseStatusCodePages 扩展方法配置的。

例如,以下示例将应用更改为使用符合 RFC 7807 的有效负载向客户端响应所有客户端和服务器响应,包括路由错误(例如)。 404 NOT FOUND 有关详细信息,请参阅 “问题详细信息 ”部分。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseStatusCodePages(async statusCodeContext 
    => await Results.Problem(statusCode: statusCodeContext.HttpContext.Response.StatusCode)
                 .ExecuteAsync(statusCodeContext.HttpContext));

app.MapGet("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

问题详细信息

问题详细信息 不是描述 HTTP API 错误的唯一响应格式,但它们通常用于报告 HTTP API 的错误。

问题详细信息服务实现 IProblemDetailsService 接口,该接口支持在 ASP.NET Core 中创建问题详细信息。 注册AddProblemDetails(IServiceCollection)默认IServiceCollection实现的扩展方法IProblemDetailsService

在 ASP.NET Core 应用中,以下中间件在调用时AddProblemDetails会生成问题详细信息 HTTP 响应,但Accept请求 HTTP 标头不包含已注册IProblemDetailsWriter的内容类型之一(默认值): application/json

可以将最小 API 应用配置为使用扩展方法为所有 HTTP 客户端和服务器错误响应生成问题详细信息响应,这些响应尚未AddProblemDetails包含正文内容。

以下代码将应用配置为生成问题详细信息:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

app.MapGet("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)));

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

有关使用 AddProblemDetails的详细信息,请参阅 问题详细信息

IProblemDetailsService 回退

在以下代码中,如果httpContext.Response.WriteAsync("Fallback: An error occurred.")实现无法生成错误,IProblemDetailsService则返回错误ProblemDetails

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp =>
{
    exceptionHandlerApp.Run(async httpContext =>
    {
        var pds = httpContext.RequestServices.GetService<IProblemDetailsService>();
        if (pds == null
            || !await pds.TryWriteAsync(new() { HttpContext = httpContext }))
        {
            // Fallback behavior
            await httpContext.Response.WriteAsync("Fallback: An error occurred.");
        }
    });
});

app.MapGet("/exception", () =>
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

前面的代码:

  • 如果 problemDetailsService 无法编写回退代码,则编写 ProblemDetails错误消息。 例如, 接受请求标头 指定不支持的 DefaulProblemDetailsWriter 媒体类型。
  • 使用 异常处理程序中间件

下面的示例与前面的示例类似,只不过它调用了 Status Code Pages middleware.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseStatusCodePages(statusCodeHandlerApp =>
{
    statusCodeHandlerApp.Run(async httpContext =>
    {
        var pds = httpContext.RequestServices.GetService<IProblemDetailsService>();
        if (pds == null
            || !await pds.TryWriteAsync(new() { HttpContext = httpContext }))
        {
            // Fallback behavior
            await httpContext.Response.WriteAsync("Fallback: An error occurred.");
        }
    });
});

app.MapGet("/users/{id:int}", (int id) =>
{
    return id <= 0 ? Results.BadRequest() : Results.Ok(new User(id));
});

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

其他错误处理功能

从控制器迁移到最小 API

如果要从基于控制器的 API 迁移到最小 API:

  1. 将作筛选器替换为终结点筛选器 或中间件
  2. 将模型验证替换为手动验证 或自定义绑定
  3. 将异常筛选器替换为 异常处理中间件
  4. 使用一致的错误响应AddProblemDetails()

何时使用基于控制器的错误处理

如果需要,请考虑基于控制器的 API:

  • 复杂的模型验证方案
  • 跨多个控制器集中处理异常
  • 对错误响应格式的精细控制
  • 与 MVC 功能(如筛选器和约定)集成

有关基于控制器的错误处理的详细信息,包括验证错误、问题详细信息自定义和异常筛选器,请参阅 “控制器 ”选项卡部分。

其他资源