在基于 ASP.NET Core 控制器的 Web API 中处理错误
注意
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
本文介绍如何在基于控制器的 ASP.NET Core Web API 中处理错误以及自定义错误处理。 有关最小 API 中的错误处理的信息,请参阅在 ASP.NET Core 中处理错误和在最小 API 中处理错误。
开发人员异常页
“开发人员异常”页显示未经处理的请求异常的详细信息。 它使用 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
若要查看开发人员异常页,请执行以下操作:
将以下控制器操作添加到基于控制器的 API。 请求终结点时,该操作会引发异常。
[HttpGet("Throw")] public IActionResult Throw() => throw new Exception("Sample exception.");
在开发环境中运行应用。
转到控制器操作定义的终结点。
异常处理程序
在非开发环境中,使用异常处理中间件来生成错误有效负载:
在
Program.cs
中,调用 UseExceptionHandler 来添加异常处理中间件:var app = builder.Build(); app.UseHttpsRedirection(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/error"); } app.UseAuthorization(); app.MapControllers(); app.Run();
配置控制器操作以响应
/error
路由:[Route("/error")] public IActionResult HandleError() => Problem();
前面的 HandleError
操作向客户端发送符合 RFC 7807 的负载。
警告
不要使用 HTTP 方法属性(如 HttpGet
)标记错误处理程序操作方法。 显式谓词可阻止某些请求访问操作方法。
对于使用 Swagger / OpenAPI 的 Web API,请使用 [ApiExplorerSettings] 属性来标记错误处理程序操作,并将它的 IgnoreApi 属性设置为 true
。 此属性配置将错误处理程序操作排除在应用的 OpenAPI 规范外:
[ApiExplorerSettings(IgnoreApi = true)]
如果要使未经身份验证的用户能够看到错误,则允许匿名访问该方法。
还可以在开发环境中使用异常处理中间件来生成所有环境中一致的有效负载格式:
在
Program.cs
中,注册特定于环境的异常处理中间件实例:if (app.Environment.IsDevelopment()) { app.UseExceptionHandler("/error-development"); } else { app.UseExceptionHandler("/error"); }
在上述代码中,通过以下方法注册了中间件:
- 开发环境中
/error-development
的路由。 - 非开发环境中
/error
的路由。
- 开发环境中
为开发和非开发路由添加控制器操作:
[Route("/error-development")] public IActionResult HandleErrorDevelopment( [FromServices] IHostEnvironment hostEnvironment) { if (!hostEnvironment.IsDevelopment()) { return NotFound(); } var exceptionHandlerFeature = HttpContext.Features.Get<IExceptionHandlerFeature>()!; return Problem( detail: exceptionHandlerFeature.Error.StackTrace, title: exceptionHandlerFeature.Error.Message); } [Route("/error")] public IActionResult HandleError() => Problem();
使用异常来修改响应
可以使用自定义异常和操作筛选器从控制器外部修改响应的内容:
创建名为
HttpResponseException
的已知异常类型:public class HttpResponseException : Exception { public HttpResponseException(int statusCode, object? value = null) => (StatusCode, Value) = (statusCode, value); public int StatusCode { get; } public object? Value { get; } }
创建名为
HttpResponseExceptionFilter
的操作筛选器:public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter { public int Order => int.MaxValue - 10; public void OnActionExecuting(ActionExecutingContext context) { } public void OnActionExecuted(ActionExecutedContext context) { if (context.Exception is HttpResponseException httpResponseException) { context.Result = new ObjectResult(httpResponseException.Value) { StatusCode = httpResponseException.StatusCode }; context.ExceptionHandled = true; } } }
上述筛选器指定最大整数值减 10 的
Order
。 此Order
允许其他筛选器在管道结束时运行。在
Program.cs
中,将操作筛选器添加到筛选器集合:builder.Services.AddControllers(options => { options.Filters.Add<HttpResponseExceptionFilter>(); });
验证失败错误响应
对于 Web API 控制器,如果模型验证失败,MVC 将使用 ValidationProblemDetails 响应类型做出响应。 MVC 使用 InvalidModelStateResponseFactory 的结果来构造验证失败的错误响应。 在 Program.cs
中,以下示例使用也支持将响应格式化为 XML 的实现替代了默认工厂:
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
new BadRequestObjectResult(context.ModelState)
{
ContentTypes =
{
// using static System.Net.Mime.MediaTypeNames;
Application.Json,
Application.Xml
}
};
})
.AddXmlSerializerFormatters();
客户端错误响应
错误结果定义为具有 HTTP 状态代码 400 或更高的结果。 对于 Web API 控制器,MVC 会转换错误结果来生成 ProblemDetails。
默认情况下,会自动为错误状态代码创建 ProblemDetails
,但可以通过以下方式之一配置错误响应:
默认问题详细信息响应
以下 Program.cs
文件由 API 控制器的 Web 应用程序模板生成:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
请考虑以下控制器,它在输入无效时返回 BadRequest:
[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
// /api/values2/divide/1/2
[HttpGet("{Numerator}/{Denominator}")]
public IActionResult Divide(double Numerator, double Denominator)
{
if (Denominator == 0)
{
return BadRequest();
}
return Ok(Numerator / Denominator);
}
// /api/values2 /squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
return BadRequest();
}
return Ok(Math.Sqrt(radicand));
}
}
以下任一条件适用时,会使用前面的代码生成问题详细信息响应:
/api/values2/divide
终结点是通过零分母调用的。/api/values2/squareroot
终结点是通过小于零的被开方数调用的。
默认问题详细信息响应正文具有以下 type
、title
和 status
值:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "Bad Request",
"status": 400,
"traceId": "00-84c1fd4063c38d9f3900d06e56542d48-85d1d4-00"
}
问题详细信息服务
ASP.NET Core 支持使用 IProblemDetailsService为 HTTP API 创建问题详细信息。 有关详细信息,请参阅问题详细信息服务。
以下代码将应用配置为为所有 尚未包含正文内容的 HTTP 客户端和服务器错误响应生成问题详细信息响应:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseExceptionHandler();
app.UseStatusCodePages();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.MapControllers();
app.Run();
请考虑上一部分中的 API 控制器,其在输入无效时返回 BadRequest:
[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
// /api/values2/divide/1/2
[HttpGet("{Numerator}/{Denominator}")]
public IActionResult Divide(double Numerator, double Denominator)
{
if (Denominator == 0)
{
return BadRequest();
}
return Ok(Numerator / Denominator);
}
// /api/values2 /squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
return BadRequest();
}
return Ok(Math.Sqrt(radicand));
}
}
以下任一条件适用时,会使用前面的代码生成问题详细信息响应:
- 提供的输入无效。
- URI 没有匹配的终结点。
- 发生未经处理的异常。
当 SuppressMapClientErrors 属性设置为 true
时,会禁止自动创建错误状态代码的 ProblemDetails
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressMapClientErrors = true;
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
使用上述代码,当 API 控制器返回 BadRequest
时,将返回 HTTP 400 响应状态,而没有响应正文。 SuppressMapClientErrors
防止创建 ProblemDetails
响应,即使在为 API 控制器终结点调用 WriteAsync
时也是如此。 本文后面将介绍 WriteAsync
。
下一部分介绍如何使用 CustomizeProblemDetails 自定义问题详细信息响应正文,以返回更有用的响应。 有关更多自定义选项,请参阅自定义问题详细信息。
使用 CustomizeProblemDetails
自定义问题详细信息
以下代码使用 ProblemDetailsOptions 设置 CustomizeProblemDetails:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddProblemDetails(options =>
options.CustomizeProblemDetails = (context) =>
{
var mathErrorFeature = context.HttpContext.Features
.Get<MathErrorFeature>();
if (mathErrorFeature is not null)
{
(string Detail, string Type) details = mathErrorFeature.MathError switch
{
MathErrorType.DivisionByZeroError =>
("Divison by zero is not defined.",
"https://wikipedia.org/wiki/Division_by_zero"),
_ => ("Negative or complex numbers are not valid input.",
"https://wikipedia.org/wiki/Square_root")
};
context.ProblemDetails.Type = details.Type;
context.ProblemDetails.Title = "Bad Input";
context.ProblemDetails.Detail = details.Detail;
}
}
);
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStatusCodePages();
app.UseAuthorization();
app.MapControllers();
app.Run();
更新的 API 控制器:
[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
// /api/values/divide/1/2
[HttpGet("{Numerator}/{Denominator}")]
public IActionResult Divide(double Numerator, double Denominator)
{
if (Denominator == 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.DivisionByZeroError
};
HttpContext.Features.Set(errorType);
return BadRequest();
}
return Ok(Numerator / Denominator);
}
// /api/values/squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
HttpContext.Features.Set(errorType);
return BadRequest();
}
return Ok(Math.Sqrt(radicand));
}
}
以下代码包含与前面的示例一起使用的 MathErrorFeature
和 MathErrorType
:
// Custom Http Request Feature
class MathErrorFeature
{
public MathErrorType MathError { get; set; }
}
// Custom math errors
enum MathErrorType
{
DivisionByZeroError,
NegativeRadicandError
}
以下任一条件适用时,会使用前面的代码生成问题详细信息响应:
/divide
终结点是通过零分母调用的。/squareroot
终结点是通过小于零的被开方数调用的。- URI 没有匹配的终结点。
当通过小于零的被开方数调用任一 squareroot
终结点时,问题详细信息响应正文包含以下内容:
{
"type": "https://en.wikipedia.org/wiki/Square_root",
"title": "Bad Input",
"status": 400,
"detail": "Negative or complex numbers are not allowed."
}
实现 ProblemDetailsFactory
MVC 使用 Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory 生成 ProblemDetails 和 ValidationProblemDetails 的所有实例。 此工厂用于:
- 客户端错误响应
- 验证失败错误响应
- ControllerBase.Problem 和 ControllerBase.ValidationProblem
若要自定义问题详细信息响应,请在 Program.cs
中注册 ProblemDetailsFactory 的自定义实现:
builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();
使用 ApiBehaviorOptions.ClientErrorMapping
使用 ClientErrorMapping 属性配置 ProblemDetails
响应的内容。 例如,Program.cs
中的以下代码会更新 404 响应的 Link 属性:
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});
其他资源
本文介绍如何处理 ASP.NET Core Web API 的错误以及自定义错误处理。
开发人员异常页
开发人员异常页显示服务器错误的详细堆栈跟踪。 它使用 DeveloperExceptionPageMiddleware 来捕获 HTTP 管道中的同步和异步异常,并生成错误响应。 例如,考虑以下控制器操作,该操作将引发异常:
[HttpGet("Throw")]
public IActionResult Throw() =>
throw new Exception("Sample exception.");
当“开发人员异常页”检测到未处理的异常时,它会生成类似以下示例的默认纯文本响应:
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
System.Exception: Sample exception.
at HandleErrorsSample.Controllers.ErrorsController.Get() in ...
at lambda_method1(Closure , Object , Object[] )
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
...
如果客户端请求 HTML 格式的响应,“开发人员异常页”将生成类似以下示例的响应:
HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Internal Server Error</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
font-size: .813em;
color: #222;
background-color: #fff;
}
h1 {
color: #44525e;
margin: 15px 0 15px 0;
}
...
若要请求 HTML 格式的响应,请将 Accept
HTTP 请求标头设置为 text/html
。
警告
仅当应用在开发环境中运行时才启用“开发人员异常页”。 当应用在生产环境中运行时,切勿公开共享详细的异常信息。 有关配置环境的详细信息,请参阅在 ASP.NET Core 中使用多个环境。
异常处理程序
在非开发环境中,使用异常处理中间件来生成错误有效负载:
在
Program.cs
中,调用 UseExceptionHandler 来添加异常处理中间件:var app = builder.Build(); app.UseHttpsRedirection(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/error"); } app.UseAuthorization(); app.MapControllers(); app.Run();
配置控制器操作以响应
/error
路由:[Route("/error")] public IActionResult HandleError() => Problem();
前面的 HandleError
操作向客户端发送符合 RFC 7807 的负载。
警告
不要使用 HTTP 方法属性(如 HttpGet
)标记错误处理程序操作方法。 显式谓词可阻止某些请求访问操作方法。
对于使用 Swagger / OpenAPI 的 Web API,请使用 [ApiExplorerSettings] 属性来标记错误处理程序操作,并将它的 IgnoreApi 属性设置为 true
。 此属性配置将错误处理程序操作排除在应用的 OpenAPI 规范外:
[ApiExplorerSettings(IgnoreApi = true)]
如果要使未经身份验证的用户能够看到错误,则允许匿名访问该方法。
还可以在开发环境中使用异常处理中间件来生成所有环境中一致的有效负载格式:
在
Program.cs
中,注册特定于环境的异常处理中间件实例:if (app.Environment.IsDevelopment()) { app.UseExceptionHandler("/error-development"); } else { app.UseExceptionHandler("/error"); }
在上述代码中,通过以下方法注册了中间件:
- 开发环境中
/error-development
的路由。 - 非开发环境中
/error
的路由。
- 开发环境中
为开发和非开发路由添加控制器操作:
[Route("/error-development")] public IActionResult HandleErrorDevelopment( [FromServices] IHostEnvironment hostEnvironment) { if (!hostEnvironment.IsDevelopment()) { return NotFound(); } var exceptionHandlerFeature = HttpContext.Features.Get<IExceptionHandlerFeature>()!; return Problem( detail: exceptionHandlerFeature.Error.StackTrace, title: exceptionHandlerFeature.Error.Message); } [Route("/error")] public IActionResult HandleError() => Problem();
使用异常来修改响应
可以使用自定义异常和操作筛选器从控制器外部修改响应的内容:
创建名为
HttpResponseException
的已知异常类型:public class HttpResponseException : Exception { public HttpResponseException(int statusCode, object? value = null) => (StatusCode, Value) = (statusCode, value); public int StatusCode { get; } public object? Value { get; } }
创建名为
HttpResponseExceptionFilter
的操作筛选器:public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter { public int Order => int.MaxValue - 10; public void OnActionExecuting(ActionExecutingContext context) { } public void OnActionExecuted(ActionExecutedContext context) { if (context.Exception is HttpResponseException httpResponseException) { context.Result = new ObjectResult(httpResponseException.Value) { StatusCode = httpResponseException.StatusCode }; context.ExceptionHandled = true; } } }
上述筛选器指定最大整数值减 10 的
Order
。 此Order
允许其他筛选器在管道结束时运行。在
Program.cs
中,将操作筛选器添加到筛选器集合:builder.Services.AddControllers(options => { options.Filters.Add<HttpResponseExceptionFilter>(); });
验证失败错误响应
对于 Web API 控制器,如果模型验证失败,MVC 将使用 ValidationProblemDetails 响应类型做出响应。 MVC 使用 InvalidModelStateResponseFactory 的结果来构造验证失败的错误响应。 在 Program.cs
中,以下示例使用也支持将响应格式化为 XML 的实现替代了默认工厂:
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
new BadRequestObjectResult(context.ModelState)
{
ContentTypes =
{
// using static System.Net.Mime.MediaTypeNames;
Application.Json,
Application.Xml
}
};
})
.AddXmlSerializerFormatters();
客户端错误响应
错误结果定义为具有 HTTP 状态代码 400 或更高的结果。 对于 Web API 控制器,MVC 会转换错误结果来生成 ProblemDetails。
可以通过以下方式之一配置错误响应:
实现 ProblemDetailsFactory
MVC 使用 Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory 生成 ProblemDetails 和 ValidationProblemDetails 的所有实例。 此工厂用于:
- 客户端错误响应
- 验证失败错误响应
- ControllerBase.Problem 和 ControllerBase.ValidationProblem
若要自定义问题详细信息响应,请在 Program.cs
中注册 ProblemDetailsFactory 的自定义实现:
builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();
使用 ApiBehaviorOptions.ClientErrorMapping
使用 ClientErrorMapping 属性配置 ProblemDetails
响应的内容。 例如,Program.cs
中的以下代码会更新 404 响应的 Link 属性:
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});
用于处理异常的自定义中间件
异常处理中间件中的默认设置适用于大多数应用。 对于需要专用异常处理的应用,请考虑自定义异常处理中间件。
为异常生成 ProblemDetails 有效负载
当发生未处理的异常时,ASP.NET Core 不会生成标准化的错误有效负载。 对于需要向客户端返回标准化 ProblemDetails 响应的场景,可以使用 ProblemDetails 中间件将异常和 404 响应映射到 ProblemDetails 有效负载。 异常处理中间件还可用于返回未处理的异常的 ProblemDetails 有效负载。
其他资源
本文介绍如何处理和自定义 ASP.NET Core Web API 的错误处理。
开发人员异常页
开发人员异常页是一种用于获取服务器错误详细堆栈跟踪的有用工具。 它使用 DeveloperExceptionPageMiddleware 来捕获 HTTP 管道中的同步和异步异常,并生成错误响应。 为了进行说明,请考虑以下控制器操作:
[HttpGet("{city}")]
public WeatherForecast Get(string city)
{
if (!string.Equals(city?.TrimEnd(), "Redmond", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException(
$"We don't offer a weather forecast for {city}.", nameof(city));
}
return GetWeather().First();
}
运行以下 curl
命令以测试前面的操作:
curl -i https://localhost:5001/weatherforecast/chicago
如果客户端不请求 HTML 格式的输出,“开发人员异常页”将显示纯文本响应。 随即显示以下输出:
HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: text/plain
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2019 16:13:16 GMT
System.ArgumentException: We don't offer a weather forecast for chicago. (Parameter 'city')
at WebApiSample.Controllers.WeatherForecastController.Get(String city) in C:\working_folder\aspnet\AspNetCore.Docs\aspnetcore\web-api\handle-errors\samples\3.x\Controllers\WeatherForecastController.cs:line 34
at lambda_method(Closure , Object , Object[] )
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
=======
Accept: */*
Host: localhost:44312
User-Agent: curl/7.55.1
要改为显示 HTML 格式的响应,请将 Accept
HTTP 请求头设置为 text/html
媒体类型。 例如:
curl -i -H "Accept: text/html" https://localhost:5001/weatherforecast/chicago
请考虑以下 HTTP 响应摘录:
HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2019 16:55:37 GMT
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Internal Server Error</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
font-size: .813em;
color: #222;
background-color: #fff;
}
通过 curl 等工具进行测试时,HTML 格式的响应会很有用。
警告
仅当应用程序在开发环境中运行时才启用开发人员异常页。 当应用在生产环境中运行时,切勿公开共享详细的异常信息。 有关配置环境的详细信息,请参阅在 ASP.NET Core 中使用多个环境。
不要使用 HTTP 方法属性(如 HttpGet
)标记错误处理程序操作方法。 显式谓词可阻止某些请求访问操作方法。 如果要使未经身份验证的用户能够看到错误,则允许匿名访问该方法。
异常处理程序
在非开发环境中,可使用异常处理中间件来生成错误负载:
在
Startup.Configure
中,调用 UseExceptionHandler 以使用中间件:public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/error"); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
配置控制器操作以响应
/error
路由:[ApiController] public class ErrorController : ControllerBase { [Route("/error")] public IActionResult Error() => Problem(); }
前面的 Error
操作向客户端发送符合 RFC 7807 的负载。
异常处理中间件还可以在本地开发环境中提供更详细的内容协商输出。 使用以下步骤在开发和生产环境中生成一致的负载格式:
在
Startup.Configure
中,注册特定于环境的异常处理中间件实例:public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseExceptionHandler("/error-local-development"); } else { app.UseExceptionHandler("/error"); } }
在上述代码中,通过以下方法注册了中间件:
- 开发环境中
/error-local-development
的路由。 - 非开发环境中
/error
的路由。
- 开发环境中
将属性路由应用于控制器操作:
[ApiController] public class ErrorController : ControllerBase { [Route("/error-local-development")] public IActionResult ErrorLocalDevelopment( [FromServices] IWebHostEnvironment webHostEnvironment) { if (webHostEnvironment.EnvironmentName != "Development") { throw new InvalidOperationException( "This shouldn't be invoked in non-development environments."); } var context = HttpContext.Features.Get<IExceptionHandlerFeature>(); return Problem( detail: context.Error.StackTrace, title: context.Error.Message); } [Route("/error")] public IActionResult Error() => Problem(); }
上述代码调用 ControllerBase.Problem 来创建 ProblemDetails 响应。
使用异常来修改响应
可以从控制器之外修改响应的内容。 在 ASP.NET 4.x Web API 中,执行此操作的一种方法是使用 HttpResponseException 类型。 ASP.NET Core 不包括等效类型。 可以使用以下步骤来添加对 HttpResponseException
的支持:
创建名为
HttpResponseException
的已知异常类型:public class HttpResponseException : Exception { public int Status { get; set; } = 500; public object Value { get; set; } }
创建名为
HttpResponseExceptionFilter
的操作筛选器:public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter { public int Order { get; } = int.MaxValue - 10; public void OnActionExecuting(ActionExecutingContext context) { } public void OnActionExecuted(ActionExecutedContext context) { if (context.Exception is HttpResponseException exception) { context.Result = new ObjectResult(exception.Value) { StatusCode = exception.Status, }; context.ExceptionHandled = true; } } }
上述筛选器指定最大整数值减 10 的
Order
。 此Order
允许其他筛选器在管道结束时运行。在
Startup.ConfigureServices
中,将操作筛选器添加到筛选器集合:services.AddControllers(options => options.Filters.Add(new HttpResponseExceptionFilter()));
验证失败错误响应
对于 Web API 控制器,如果模型验证失败,MVC 将使用 ValidationProblemDetails 响应类型做出响应。 MVC 使用 InvalidModelStateResponseFactory 的结果来构造验证失败的错误响应。 下面的示例使用工厂在 Startup.ConfigureServices
中将默认响应类型更改为 SerializableError:
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var result = new BadRequestObjectResult(context.ModelState);
// TODO: add `using System.Net.Mime;` to resolve MediaTypeNames
result.ContentTypes.Add(MediaTypeNames.Application.Json);
result.ContentTypes.Add(MediaTypeNames.Application.Xml);
return result;
};
});
客户端错误响应
错误结果定义为具有 HTTP 状态代码 400 或更高的结果。 对于 Web API 控制器,MVC 会将错误结果转换为具有 ProblemDetails 的结果。
可以通过以下方式之一配置错误响应:
实现 ProblemDetailsFactory
MVC 使用 Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory 生成 ProblemDetails 和 ValidationProblemDetails 的所有实例。 此工厂用于:
- 客户端错误响应
- 验证失败错误响应
- ControllerBase.Problem 和 ControllerBase.ValidationProblem
若要自定义问题详细信息响应,请在 Startup.ConfigureServices
中注册 ProblemDetailsFactory 的自定义实现:
public void ConfigureServices(IServiceCollection serviceCollection)
{
services.AddControllers();
services.AddTransient<ProblemDetailsFactory, CustomProblemDetailsFactory>();
}
使用 ApiBehaviorOptions.ClientErrorMapping
使用 ClientErrorMapping 属性配置 ProblemDetails
响应的内容。 例如,Startup.ConfigureServices
中的以下代码会更新 404 响应的 type
属性:
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
options.DisableImplicitFromServicesParameters = true;
});
用于处理异常的自定义中间件
异常处理中间件中的默认设置适用于大多数应用。 对于需要专用异常处理的应用,请考虑自定义异常处理中间件。
为异常生成 ProblemDetails 有效负载
当发生未处理的异常时,ASP.NET Core 不会生成标准化的错误有效负载。 对于需要向客户端返回标准化 ProblemDetails 响应的场景,可以使用 ProblemDetails 中间件将异常和 404 响应映射到 ProblemDetails 有效负载。 异常处理中间件还可用于返回未处理的异常的 ProblemDetails 有效负载。