Skip to content

Pass through response status codes from BadHttpRequestException in ExceptionHandlerMiddleware #43831

Open
@DamianEdwards

Description

@DamianEdwards

The default error handler for ExceptionHandlerMiddleware always sets the response status code to 500. It is not uncommon for BadHttpRequestException to be thrown by requests though which detail a different (usually 4xx) status code but this will get masked by the default error handler.

While it's possible to customize the error handler via setting ExceptionHandlerOptions.ExceptionHandler, one has to reimplement the default feature logic for returning JSON Problem Details responses manually which is quite a bit of code, e.g.:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddProblemDetails(options =>
{
    options.CustomizeProblemDetails = static ctx =>
        ctx.ProblemDetails.Extensions["requestId"] = Activity.Current?.Id ?? ctx.HttpContext.TraceIdentifier;
});

var app = builder.Builder();

if (!app.Environment.IsDevelopment())
{
    // Error handling
    app.UseExceptionHandler(new ExceptionHandlerOptions
    {
        AllowStatusCode404Response = true,
        ExceptionHandler = async (HttpContext context) =>
        {
            // Pass-through status codes from BadHttpRequestException
            var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
            var error = exceptionHandlerFeature?.Error;

            if (error is BadHttpRequestException badRequestEx)
            {
                context.Response.StatusCode = badRequestEx.StatusCode;
            }

            if (context.RequestServices.GetRequiredService<IProblemDetailsService>() is { } problemDetailsService)
            {
                await problemDetailsService.WriteAsync(new()
                {
                    HttpContext = context,
                    AdditionalMetadata = exceptionHandlerFeature?.Endpoint?.Metadata,
                    ProblemDetails = { Status = context.Response.StatusCode }
                });
            }
            else if (ReasonPhrases.GetReasonPhrase(context.Response.StatusCode) is { } reasonPhrase)
            {
                await context.Response.WriteAsync(reasonPhrase);
            }
        }
    });
}

app.MapGet("/throw/{statusCode?}", (int? statusCode) =>
    {
        throw statusCode switch
        {
            >= 400 and < 500 => new BadHttpRequestException(
                $"{statusCode} {ReasonPhrases.GetReasonPhrase(statusCode.Value)}",
                statusCode.Value),
            _ => new Exception("uh oh")
        };
    });

app.Run();

We should consider adding support for passing through the response status code from BadHttpRequestException in the default error handler. We can consider adding a boolean property to ExceptionHandlerOptions to control this behavior if necessary.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-middlewareIncludes: URL rewrite, redirect, response cache/compression, session, and other general middlewaresfeature-diagnosticsDiagnostic middleware and pages (except EF diagnostics)

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions