1

I'm trying to create an ASP.NET Core 8 Minimal API endpoint and I'm trying to get it to return HttpProblemDetails when the query string parameters are invalid. Of course, I can implement my own validation, but I'm trying to leverage the framework for this.

Basically, if I have a query string parameter (for example an integer) and the input is not a valid integer, then I do get back a 'bad request', but without any body. This makes it difficult for the consumer to understand what exactly is wrong.

Here's a repro as a C# unit test (I'm using TestServer, but I get the same result when I use kestrel):

using System.Net.Http;
using System.Net;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Shouldly;

namespace TestProject1
{
    public class HttpProblemDetailsTests
    {
        [Fact]
        public async Task Should_return_problem_details_if_querystring_not_provided()
        {
            var (host, testClient) = await SetupWebApp();

            var response = await testClient.GetAsync("/problem");
            response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
            var text = await response.Content.ReadAsStringAsync();
            
            // Fails on this line
            text.ShouldNotBeEmpty();

            await host.StopAsync();
        }
        
        [Fact]
        public async Task Returns_ok_if_querystring_provided()
        {
            var (host, testClient) = await SetupWebApp();

            var response = await testClient.GetAsync("/problem?abc=123");

            response.StatusCode.ShouldBe(HttpStatusCode.OK);
            await host.StopAsync();
        }

        private async Task<(IHost host, HttpClient testClient)> SetupWebApp()
        {
            var b = new HostBuilder()
                .ConfigureWebHost(c =>
                {
                    c.UseTestServer();
                    c.ConfigureServices(services =>
                    {
                        services.AddProblemDetails(); 
                        services.AddRouting();
                    });
                    c.Configure(app =>
                    {
                        app.UseExceptionHandler();

                        app.UseRouting();
                        app.UseEndpoints(endpoints =>
                        {
                            endpoints.MapGet("/problem", Problem);
                        });
                    });
                });

            var host = await b.StartAsync();

            var testClient = host.GetTestClient();
            testClient.DefaultRequestHeaders.Add("Accept", "application/json");
            return (host, testClient);
        }

        public class MyParameters
        {
            public int Abc { get; set; }
        }

        private async Task<IResult> Problem([AsParameters] MyParameters p)
        {
            return Results.Text("Hello, World!");
        }
    }
}

I have tried several other things:

  1. Add my own IProblemDetailsWriter implementation, but it doesn't seem to hit this
  2. Use [FromQuery] and not [AsParameters]. This produces the same result
  3. I've followed the example here, but that also produces the exact same result: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/handle-errors?view=aspnetcore-8.0#problem-details
2
  • You can configure routing middleware to throw on bad request. This will allow you to carch the exception and add a body. Commented Aug 22, 2024 at 15:21
  • @beautifulcoder, how would you do that? I don't see any options for that on the RouteOptions class, nor if I look in the EndpointMiddleware. Commented Aug 23, 2024 at 6:00

1 Answer 1

1

You'll need to throw on a bad request first:

var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<RouteHandlerOptions>(options =>
{
    options.ThrowOnBadRequest = true;
});

Then, once an exception is thrown. You should be able to wire an exception handler using middleware.

app.UseExceptionHandler("/error");

app.Map("/error", (HttpContext context) =>
{
    var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
    if (exception is BadHttpRequestException badRequestException)
    {
        return Results.BadRequest(new ErrorResponse
        {
            Error = "Invalid Parameter",
            Details = badRequestException.Message
        });
    }

    return Results.Problem("An unexpected error occurred.");
});

The idea is to capture as much detail as you can and return a response with a body.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks. I ended up solving the handling of the error this way: ''' services.AddProblemDetails(opt => opt.CustomizeProblemDetails = context => { if (context.Exception is BadHttpRequestException ex) { context.HttpContext.Response.StatusCode = 400; context.ProblemDetails.Detail = ex.Message; context.ProblemDetails.Status = 400; } }); '''
but the 'ThrowOnBadRequest' was definetly the trick. Should be more prominent in the documentation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.