使用[AsParameters]时,ASP.NET Core 8 Minimal API如何返回HttpProblemDetails?
解决ASP.NET Core 8 Minimal API查询参数无效时返回HttpProblemDetails的问题
问题分析
在ASP.NET Core 8 Minimal API中,当查询参数(如整数类型)缺失或格式无效时,框架默认仅返回400 Bad Request状态码,但无响应体,导致客户端无法明确错误原因。你尝试过自定义IProblemDetailsWriter、切换[FromQuery]等方法,但未触发预期的ProblemDetails响应。
解决方案
要让框架自动返回包含详细错误信息的HttpProblemDetails,需要调整中间件配置和问题详情服务的设置,确保参数绑定/验证错误能被正确捕获并格式化。
1. 配置ProblemDetails服务
在ConfigureServices中,通过AddProblemDetails自定义错误详情的生成逻辑,确保绑定错误的信息被包含进去:
services.AddProblemDetails(options => { options.CustomizeProblemDetails = ctx => { // 处理400错误的参数绑定异常 if (ctx.HttpContext.Response.StatusCode == StatusCodes.Status400BadRequest) { var badRequestEx = ctx.Exception as BadHttpRequestException; if (badRequestEx != null) { ctx.ProblemDetails.Title = "请求参数无效"; ctx.ProblemDetails.Detail = badRequestEx.Message; ctx.ProblemDetails.Status = StatusCodes.Status400BadRequest; ctx.ProblemDetails.Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"; } } }; });
2. 调整中间件顺序并添加必要组件
确保异常处理和状态码页面中间件的顺序正确,同时添加数据注解支持以启用模型验证:
services.AddMvcCore().AddDataAnnotations(); // 支持数据注解验证 services.AddRouting();
在Configure方法中,将UseExceptionHandler和UseStatusCodePages放在UseRouting之前,确保错误能被提前捕获:
app.UseExceptionHandler(); // 捕获异常并生成ProblemDetails app.UseStatusCodePages(); // 将状态码转换为标准化问题响应 app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGet("/problem", Problem); });
3. (可选)增强模型验证
如果需要更精细的参数验证,可以在参数类中添加数据注解,比如强制要求参数存在:
public class MyParameters { [BindRequired(ErrorMessage = "参数abc是必填项")] public int Abc { get; set; } }
修改后的完整测试代码
using System.Net.Http; using System.Net; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.TestServer; 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(); text.ShouldNotBeEmpty(); text.ShouldContain("请求参数无效"); 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(); } [Fact] public async Task Should_return_problem_details_if_querystring_invalid() { var (host, testClient) = await SetupWebApp(); var response = await testClient.GetAsync("/problem?abc=invalid"); response.StatusCode.ShouldBe(HttpStatusCode.BadRequest); var text = await response.Content.ReadAsStringAsync(); text.ShouldNotBeEmpty(); text.ShouldContain("无法将字符串转换为整数"); await host.StopAsync(); } private async Task<(IHost host, HttpClient testClient)> SetupWebApp() { var b = new HostBuilder() .ConfigureWebHost(c => { c.UseTestServer(); c.ConfigureServices(services => { services.AddProblemDetails(options => { options.CustomizeProblemDetails = ctx => { if (ctx.HttpContext.Response.StatusCode == StatusCodes.Status400BadRequest) { var badRequestEx = ctx.Exception as BadHttpRequestException; if (badRequestEx != null) { ctx.ProblemDetails.Title = "请求参数无效"; ctx.ProblemDetails.Detail = badRequestEx.Message; ctx.ProblemDetails.Status = StatusCodes.Status400BadRequest; ctx.ProblemDetails.Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"; } } }; }); services.AddMvcCore().AddDataAnnotations(); services.AddRouting(); }); c.Configure(app => { app.UseExceptionHandler(); app.UseStatusCodePages(); 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 { [BindRequired(ErrorMessage = "参数abc是必填项")] public int Abc { get; set; } } private async Task<IResult> Problem([AsParameters] MyParameters p) { return Results.Text("Hello, World!"); } } }
关键说明
UseExceptionHandler用于捕获参数绑定过程中抛出的BadHttpRequestException(如格式错误、缺失必填参数),并通过ProblemDetailsService生成标准化响应。UseStatusCodePages确保即使没有抛出异常的状态码(如部分验证错误)也能转换为ProblemDetails格式。AddMvcCore().AddDataAnnotations()启用数据注解验证,支持[BindRequired]等属性的校验逻辑。
内容的提问来源于stack exchange,提问作者Erwin van der Valk
相关产品推荐
相关产品推荐

