处理 ASP.NET Core API 中的错误

注释

此版本不是本文的最新版本。 有关当前版本,请参阅本文的 .NET 10 版本

警告

不再支持此版本的 ASP.NET Core。 有关详细信息,请参阅 .NET 和 .NET 核心支持策略。 有关当前版本,请参阅本文的 .NET 10 版本

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

开发人员异常页

“开发人员异常”页显示未经处理的请求异常的详细信息。 它使用 DeveloperExceptionPageMiddleware 来捕获 HTTP 管道中的同步和异步异常,并生成错误响应。 开发人员异常页在中间件管道的早期阶段运行,以捕获后续中间件中引发的未经处理的异常。

ASP.NET Core应用在以下两者中默认启用开发人员异常页:

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

警告

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

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

  • 堆栈跟踪
  • 查询字符串参数(如果有)
  • Cookies(如果有)
  • 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 中查看开发人员异常页,请执行以下作:

  • 在环境中运行示例应用Development
  • 转至 /exception 终结点。

本部分介绍以下示例应用,演示在最小 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 OK时,json端点会生成User,其id采用0表示形式;否则返回400 BAD REQUEST状态代码,且不包含响应正文。 有关创建响应的详细信息,请参阅 “在最小 API 应用中创建响应”。

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

例如,以下示例将应用更改为针对所有客户端和服务器响应(包括路由错误,例如 )向客户端返回符合 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 客户端和服务器错误响应,如果其 ,则生成问题详细信息响应。

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

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.") 实现无法生成 IProblemDetailsServiceProblemDetails 会返回错误:

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,则使用回退代码写入一条错误消息。 例如,某个终结点的 Accept 请求标头 指定了 DefaultProblemDetailsWriter 不支持的媒体类型。
  • 使用 异常处理程序中间件

注释

DefaultProblemDetailsWriterAccept 请求标头中支持以下媒体类型:

  • application/json
  • application/problem+json
  • 通配符类型,例如 */*application/*

非 JSON 媒体类型(例如 application/xmltext/html受支持,并会触发回退行为。

下面的示例与前面的示例类似,只不过它调用了 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 功能(如筛选器和约定)集成

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

其他资源