自定义IRouteConstraint校验失败时返回ProblemDetails响应而非默认404
原代码问题
IRouteConstraint.Match 方法要求返回布尔值,你返回ProblemDetails属于语法错误。且默认路由约束匹配失败时,ASP.NET Core会判定无匹配路由直接返回404,不会触发自定义响应逻辑,需要调整实现方式。
方案1:改造路由约束实现需求
1. 重写约束逻辑
校验失败时直接向当前HttpContext写入400响应,再返回false终止路由匹配,框架会直接返回你写入的响应内容,不会 fallback 到404:
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Mvc; public class CountryRouteConstraint : IRouteConstraint { private readonly List<string> _countryCodes = new() { "AF", "AX", "AL", "DZ", "AS", "AD", "AO" /*...*/ }; public bool Match(HttpContext context, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { // 只处理入站请求,跳过URL生成场景的校验 if (routeDirection != RouteDirection.IncomingRequest) return true; values.TryGetValue(routeKey, out var value); var country = (value as string ?? string.Empty).ToUpper(); if (_countryCodes.Contains(country)) return true; // 直接写入400 + ProblemDetails响应 context.Response.StatusCode = StatusCodes.Status400BadRequest; context.Response.WriteAsJsonAsync(new ProblemDetails { Status = StatusCodes.Status400BadRequest, Title = $@"无有效国家代码:""{country}""", Instance = context.Request.Path }).Wait(); return false; } }
2. 注册约束
在Program.cs中注册自定义路由约束:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddRouting(options => { // 给约束起别名,后续路由模板可以直接用 :country 调用 options.ConstraintMap.Add("country", typeof(CountryRouteConstraint)); }); // 其余服务注册... var app = builder.Build(); // 中间件配置... app.Run();
3. 路由中使用约束
// 最小API示例 app.MapGet("/api/countries/{country:country}", (string country) => { return Results.Ok($"请求国家代码:{country}"); }); // 控制器路由示例 [Route("api/[controller]")] public class CountriesController : ControllerBase { [HttpGet("{country:country}")] public IActionResult Get(string country) { return Ok(country); } }
方案2:端点过滤器实现(.NET 7+ 推荐)
路由约束的Match是同步方法,WriteAsJsonAsync().Wait()存在低概率线程阻塞风险,高并发场景更推荐用异步的端点过滤器实现,不会干扰路由匹配逻辑:
1. 实现过滤器
public class CountryCodeValidationFilter : IEndpointFilter { private readonly List<string> _countryCodes = new() { "AF", "AX", "AL", "DZ", "AS", "AD", "AO" /*...*/ }; public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) { // 提取路由参数中的country值 if (!context.HttpContext.Request.RouteValues.TryGetValue("country", out var value) || value is not string country || !_countryCodes.Contains(country.ToUpper())) { return Results.BadRequest(new ProblemDetails { Status = StatusCodes.Status400BadRequest, Title = $@"无有效国家代码:""{country}""", Instance = context.HttpContext.Request.Path }); } return await next(context); } }
2. 端点挂载过滤器
app.MapGet("/api/countries/{country}", (string country) => { return Results.Ok($"请求国家代码:{country}"); }).AddEndpointFilter<CountryCodeValidationFilter>();
内容的提问来源于stack exchange,提问作者Marcel
相关产品推荐
相关产品推荐

