在“开发”环境中运行时,开发人员异常页面现在默认打开(在 .NET 6 中)(即为IWebHostEnvironment.IsDevelopment()
真)。
通常,在 ASP.NET Core 应用程序中配置异常处理时,在开发与生产(或更严格地说,非开发)期间将采用不同的异常处理路径。开发者异常页面(顾名思义)仅用于开发,而异常处理程序中间件 ( app.UseExceptionHandler
) 用于非开发场景。
要返回异常的问题详细信息响应,您需要分别配置两个路径。开发者异常页面通过接口有一个插件模型IDeveloperPageExceptionFilter
,可用于控制异常的呈现方式。下面是一个过滤器示例,当客户端指示它支持 JSON 时,该过滤器将异常呈现为问题详细信息:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Diagnostics;
/// <summary>
/// Formats <see cref="DeveloperExceptionPageMiddleware"/> exceptions as JSON Problem Details if the client indicates it accepts JSON.
/// </summary>
public class ProblemDetailsDeveloperPageExceptionFilter : IDeveloperPageExceptionFilter
{
private static readonly object ErrorContextItemsKey = new object();
private static readonly MediaTypeHeaderValue _jsonMediaType = new MediaTypeHeaderValue("application/json");
private static readonly RequestDelegate _respondWithProblemDetails = RequestDelegateFactory.Create((HttpContext context) =>
{
if (context.Items.TryGetValue(ErrorContextItemsKey, out var errorContextItem) && errorContextItem is ErrorContext errorContext)
{
return new ErrorProblemDetailsResult(errorContext.Exception);
}
return null;
}).RequestDelegate;
public async Task HandleExceptionAsync(ErrorContext errorContext, Func<ErrorContext, Task> next)
{
var headers = errorContext.HttpContext.Request.GetTypedHeaders();
var acceptHeader = headers.Accept;
if (acceptHeader?.Any(h => h.IsSubsetOf(_jsonMediaType)) == true)
{
errorContext.HttpContext.Items.Add(ErrorContextItemsKey, errorContext);
await _respondWithProblemDetails(errorContext.HttpContext);
}
else
{
await next(errorContext);
}
}
}
internal class ErrorProblemDetailsResult : IResult
{
private readonly Exception _ex;
public ErrorProblemDetailsResult(Exception ex)
{
_ex = ex;
}
public async Task ExecuteAsync(HttpContext httpContext)
{
var problemDetails = new ProblemDetails
{
Title = $"An unhandled exception occurred while processing the request",
Detail = $"{_ex.GetType().Name}: {_ex.Message}",
Status = _ex switch
{
BadHttpRequestException ex => ex.StatusCode,
_ => StatusCodes.Status500InternalServerError
}
};
problemDetails.Extensions.Add("exception", _ex.GetType().FullName);
problemDetails.Extensions.Add("stack", _ex.StackTrace);
problemDetails.Extensions.Add("headers", httpContext.Request.Headers.ToDictionary(kvp => kvp.Key, kvp => (string)kvp.Value));
problemDetails.Extensions.Add("routeValues", httpContext.GetRouteData().Values);
problemDetails.Extensions.Add("query", httpContext.Request.Query);
var endpoint = httpContext.GetEndpoint();
if (endpoint != null)
{
var routeEndpoint = endpoint as RouteEndpoint;
var httpMethods = endpoint?.Metadata.GetMetadata<IHttpMethodMetadata>()?.HttpMethods;
problemDetails.Extensions.Add("endpoint", new {
endpoint?.DisplayName,
routePattern = routeEndpoint?.RoutePattern.RawText,
routeOrder = routeEndpoint?.Order,
httpMethods = httpMethods != null ? string.Join(", ", httpMethods) : ""
});
}
var result = Results.Json(problemDetails, statusCode: problemDetails.Status, contentType: "application/problem+json");
await result.ExecuteAsync(httpContext);
}
}
public static class ProblemDetailsDeveloperPageExtensions
{
/// <summary>
/// Adds a <see cref="IDeveloperPageExceptionFilter"/> that formats all exceptions as JSON Problem Details to clients
/// that indicate they support JSON via the Accepts header.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/></param>
/// <returns>The <see cref="IServiceCollection"/></returns>
public static IServiceCollection AddProblemDetailsDeveloperPageExceptionFilter(this IServiceCollection services) =>
services.AddSingleton<IDeveloperPageExceptionFilter, ProblemDetailsDeveloperPageExceptionFilter>();
}
您像这样在 DI 中注册它:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetailsDeveloperPageExceptionFilter();
var app = builder.Build();
对于非开发场景,您可以注册自己的端点,用于处理异常并在那里实现所需的行为,或者您可以使用像这样的中间件。
要自己做,您将注册异常处理程序中间件并将其指向您的错误端点,该端点被写入返回问题详细信息,如下所示:
...
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error");
}
var problemJsonMediaType = new MediaTypeHeaderValue("application/problem+json");
app.MapGet("/error", (HttpContext context) =>
{
var error = context.Features.Get<IExceptionHandlerFeature>()?.Error;
var badRequestEx = error as BadHttpRequestException;
var statusCode = badRequestEx?.StatusCode ?? StatusCodes.Status500InternalServerError;
if (context.Request.GetTypedHeaders().Accept?.Any(h => problemJsonMediaType.IsSubsetOf(h)) == true)
{
// JSON Problem Details
return error switch
{
BadHttpRequestException ex => Results.Extensions.Problem(detail: ex.Message, statusCode: ex.StatusCode),
_ => Results.Extensions.Problem()
};
}
// Plain text
context.Response.StatusCode = statusCode;
return Results.Text(badRequestEx?.Message ?? "An unhandled exception occurred while processing the request.");
})
.ExcludeFromDescription();
...
请注意,由于即将在 ASP.NET Core 6.0 的 rc.2 版本中修复的问题,此示例中的某些示例现在使用自定义IResult
实现