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:
IProblemDetailsWriter
implementation, but it doesn't seem to hit this[FromQuery]
and not [AsParameters]
. This produces the same resultYou'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.