如何处理最小 API 应用中的错误
注意
此版本不是本文的最新版本。 有关当前版本,请参阅本文的 .NET 9 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本,请参阅本文的 .NET 9 版本。
由 David Acker 供稿
本文介绍如何处理最小 API 应用中的错误。 有关基于控制器的 API 中的错误处理的信息,请参阅处理 ASP.NET 核心中的错误,以及处理基于 ASP.NET 核心控制器的 Web API 中的错误。
异常
在最小 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();
开发人员异常页
“开发人员异常”页显示未经处理的请求异常的详细信息。 它使用 DeveloperExceptionPageMiddleware 来捕获 HTTP 管道中的同步和异步异常,并生成错误响应。 开发人员异常页运行在中间件管道的前面部分,以便它能够捕获随后中间件中抛出的未经处理的异常。
ASP.NET Core 应用在以下情况下默认启用开发人员异常页:
- 在开发环境中运行。
- 该应用是使用当前模板(即使用 WebApplication.CreateBuilder)创建的。
使用早期模板(即使用 WebHost.CreateDefaultBuilder)创建的应用可以通过调用 app.UseDeveloperExceptionPage
来启用开发人员异常页。
警告
仅当应用在开发环境中运行时才启用“开发人员异常页”。 当应用在生产环境中运行时,切勿公开共享详细的异常信息。 有关配置环境的详细信息,请参阅在 ASP.NET Core 中使用多个环境。
开发人员异常页可能包括关于异常和请求的以下信息:
- 堆栈跟踪
- 查询字符串参数(如果有)
- Cookie(如有)
- 标头
- 终结点元数据(如果有)
不保证开发人员异常页会提供任何信息。 使用日志记录获取完整的错误信息。
下图显示了一个示例开发人员异常页面,通过动画来显示选项卡和展示的信息:
在响应带有 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
要查看开发人员异常页,请:
- 在开发环境中运行示例应用。
- 转到
/exception
终结点。
异常处理程序
在非开发环境中,使用异常处理程序中间件来生成错误有效负载。 若要配置 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);
当 id
大于 0
时,终结点 /users
生成 200 OK
,其中 json
的表现形式为 User
,否则 400 BAD REQUEST
状态代码无响应正文。 有关创建响应的详细信息,请参阅在最小 API 应用中创建响应。
对于所有 HTTP 客户端 (400
-499
) 或服务器 (500
-599
) 响应,可配置 Status Code Pages middleware
以生成常见的正文内容(若为空)。 中间件是通过调用 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 中创建问题详细信息。 IServiceCollection 上的 AddProblemDetails(IServiceCollection) 扩展方法注册默认 IProblemDetailsService
实现。
在 ASP.NET Core 应用中,下列中间件会在调用 AddProblemDetails
时生成问题详细信息 HTTP 响应,除非 Accept
请求 HTTP 标头不包含注册的 IProblemDetailsWriter 支持的内容类型之一(默认:application/json
):
- ExceptionHandlerMiddleware:未定义自定义处理程序时生成问题详细信息响应。
- StatusCodePagesMiddleware:默认生成问题详细信息响应。
- DeveloperExceptionPageMiddleware:当
Accept
请求 HTTP 标头不包含text/html
时,在开发中生成问题详细信息响应。
可以使用 AddProblemDetails
扩展方法配置最小 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 回退
在下面的代码中,如果 IProblemDetailsService 实现无法生成 ProblemDetails,则 httpContext.Response.WriteAsync("Fallback: An error occurred.")
返回错误:
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 请求头指定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 应用,以演示处理异常的几种方法。 请求终结点 /exception
时,它会引发异常:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Map("/exception", ()
=> { throw new InvalidOperationException("Sample Exception"); });
app.Run();
开发人员异常页
开发人员异常页显示服务器错误的详细堆栈跟踪。 它使用 DeveloperExceptionPageMiddleware 来捕获 HTTP 管道中的同步和异步异常,并生成错误响应。
ASP.NET Core 应用在以下情况下默认启用开发人员异常页:
- 在开发环境中运行。
- 应用正在使用 WebApplication.CreateBuilder。
有关配置中间件的详细信息,请参阅最小 API 应用中的中间件。
使用上述最小 API 应用时,当 Developer Exception Page
检测到未处理的异常时,它会生成类似以下示例的默认纯文本响应:
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Date: Thu, 27 Oct 2022 18:00:59 GMT
Server: Kestrel
Transfer-Encoding: chunked
System.InvalidOperationException: Sample Exception
at Program.<>c.<<Main>$>b__0_1() in ....:line 17
at lambda_method2(Closure, Object, HttpContext)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
HEADERS
=======
Accept: */*
Connection: keep-alive
Host: localhost:5239
Accept-Encoding: gzip, deflate, br
警告
仅当应用在开发环境中运行时才启用“开发人员异常页”。 当应用在生产环境中运行时,切勿公开共享详细的异常信息。 有关配置环境的详细信息,请参阅在 ASP.NET Core 中使用多个环境。
异常处理程序
在非开发环境中,使用异常处理程序中间件来生成错误有效负载。 若要配置 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.Map("/exception", ()
=> { throw new InvalidOperationException("Sample Exception"); });
app.Run();
客户端和服务器错误响应
请考虑以下最小 API 应用。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Map("/users/{id:int}", (int id)
=> id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );
app.Run();
public record User(int Id);
当 id
大于 0
时,终结点 /users
生成 200 OK
,其中 json
的表现形式为 User
,否则 400 BAD REQUEST
状态代码无响应正文。 有关创建响应的详细信息,请参阅在最小 API 应用中创建响应。
对于所有 HTTP 客户端 (400
-499
) 或服务器 (500
-599
) 响应,可配置 Status Code Pages middleware
以生成常见的正文内容(若为空)。 中间件是通过调用 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.Map("/users/{id:int}", (int id)
=> id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );
app.Run();
public record User(int Id);
问题详细信息
问题详细信息并不是描述 HTTP API 错误的唯一响应格式,但它们通常用于报告 HTTP API 的错误。
问题详细信息服务实现 IProblemDetailsService 接口,该接口支持在 ASP.NET Core 中创建问题详细信息。 IServiceCollection 上的 AddProblemDetails(IServiceCollection) 扩展方法注册默认 IProblemDetailsService
实现。
在 ASP.NET Core 应用中,下列中间件会在调用 AddProblemDetails
时生成问题详细信息 HTTP 响应,除非 Accept
请求 HTTP 标头不包含注册的 IProblemDetailsWriter 支持的内容类型之一(默认:application/json
):
- ExceptionHandlerMiddleware:未定义自定义处理程序时生成问题详细信息响应。
- StatusCodePagesMiddleware:默认生成问题详细信息响应。
- DeveloperExceptionPageMiddleware:当
Accept
请求 HTTP 标头不包含text/html
时,在开发中生成问题详细信息响应。
可以使用 AddProblemDetails
扩展方法,将最小 API 应用配置为针对尚未包含正文内容的所有 HTTP 客户端和服务器错误响应生成问题详细信息响应。
以下代码配置应用以生成问题详细信息:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseExceptionHandler();
app.UseStatusCodePages();
app.Map("/users/{id:int}", (int id)
=> id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );
app.Map("/exception", ()
=> { throw new InvalidOperationException("Sample Exception"); });
app.Run();
有关使用 AddProblemDetails
的详细信息,请参阅问题详细信息