приложение / проблема + json и .net 6 минимальных API?
Я создал очень простой проект, который просто генерирует исключение, ожидая получить ответ application/problem+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();
Я не смог найти ничего подходящего в поиске в Google, что, как я полагаю, является результатом того, что это было очень свежо.
Изменится ли поведение, если его не запустить в Visual Studio? Могу ли я изменить его, чтобы получить приложение / проблему +json (также) во время работы в Visual Studio?
1 ответ
The developer exception page is now on by default (in .NET 6) when running in the "Development" environment (i.e.
IWebHostEnvironment.IsDevelopment()
is true).
Typically when configuring exception handling in an ASP.NET Core application there will be separate paths taken for exception handling during development vs. production (or more strictly, non-development). The developer exception page (as the name suggests) is only intended for use in development, whereas the exception handler middleware (
app.UseExceptionHandler
) is intended for non-development scenarios.
To return problem details responses for exceptions, you'll need to configure both paths separately. The developer exception page has a plug-in model via the
IDeveloperPageExceptionFilter
interface that can be used to take control of how exceptions are rendered. Here's an example of a filter that renders the exception as problem details when the client indicates it supports 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>();
}
You register it in DI like so:
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();
...
Обратите внимание, что в некоторых из этого примера используется пользовательский
IResult
реализация прямо сейчас из-за проблемы, которая исправляется в предстоящем выпуске rc.2 ASP.NET Core 6.0