我创建了一个非常基本的项目,只是抛出一个异常,期望得到一个应用程序/问题+json响应,但相反,我得到一个带有错误细节的html页面。我猜这是"开发人员异常"页面。我在一些帖子里读到过,但我并没有把它添加到创业中。
完整代码示例:
using Microsoft.AspNetCore.Builder;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => {
throw new System.Exception("Hello, World!");
return new string[] { };
});
app.Run();
我在谷歌上找不到任何相关的东西,我想这是由于这是非常新鲜的。
如果不在visual studio中运行,行为会改变吗?我可以改变它,所以我得到应用程序/问题+json(也),而在visual studio运行?
在.NET 6中,当运行在" Development"环境(即IWebHostEnvironment.IsDevelopment()
为真)。
通常在ASP中配置异常处理时。在开发阶段和生产阶段(或者更严格地说,非开发阶段),异常处理将采用不同的路径。开发人员异常页(顾名思义)仅用于开发中,而异常处理程序中间件(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();
...
请注意,由于即将到来的rc中正在修复的一个问题,这个示例中的一些现在使用自定义IResult
实现。2 .发布ASP。. NET Core 6.0