ASP.NET Core REST API如何在创建资源前提前返回406 Not Acceptable?
问题原因
默认ASP.NET Core的内容协商逻辑在结果执行阶段才会触发,也就是Action方法执行完毕、准备生成响应报文时才会校验Accept头是否匹配支持的媒体类型,这就会导致非幂等的POST请求先执行业务逻辑写库,之后才返回406的问题。
解决方案
通过自定义操作过滤器,将Accept头校验逻辑提前到Action方法执行前触发,匹配失败直接返回406,不会进入业务逻辑。
步骤1:实现校验过滤器
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Net.Http.Headers; public class AcceptHeaderValidationFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { // 仅校验控制器/Action标注了[Produces]的接口 if (context.ActionDescriptor is not ControllerActionDescriptor controllerActionDescriptor) return; var producesAttributes = controllerActionDescriptor.MethodInfo.GetCustomAttributes(true) .Concat(controllerActionDescriptor.ControllerTypeInfo.GetCustomAttributes(true)) .OfType<ProducesAttribute>() .ToList(); if (!producesAttributes.Any()) return; // 收集接口声明支持的所有响应媒体类型 var supportedMediaTypes = producesAttributes .SelectMany(p => p.ContentTypes) .Select(ct => ct.ToString()) .ToHashSet(StringComparer.OrdinalIgnoreCase); // 无Accept头时遵循HTTP规范默认放行 var acceptHeaders = context.HttpContext.Request.GetTypedHeaders().Accept; if (acceptHeaders == null || !acceptHeaders.Any()) return; // 校验是否存在匹配的、权重大于0的媒体类型 var hasValidMatch = acceptHeaders .Where(h => h.Quality == null || h.Quality > 0) .Any(h => supportedMediaTypes.Contains(h.MediaType.Value) // 不需要支持通配符Accept可以删掉下行逻辑 || h.MediaType.Value == "*/*" ); if (!hasValidMatch) { context.Result = new StatusCodeResult(StatusCodes.Status406NotAcceptable); } } public void OnActionExecuted(ActionExecutedContext context) { // 无需实现 } }
步骤2:注册全局过滤器
在服务注册时把过滤器添加到MVC配置中即可全局生效:
services.AddControllers(configure => { configure.ReturnHttpNotAcceptable = true; // 注册Accept头校验过滤器 configure.Filters.Add<AcceptHeaderValidationFilter>(); }) .AddNewtonsoftJson(setup => { setup.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); }) .AddXmlDataContractSerializerFormatters();
注意事项
- 过滤器只会对标注了
[Produces]特性的控制器或Action生效,无该特性的接口不会触发校验 - 默认支持
*/*通配符Accept头,不需要严格匹配的话可以保留该逻辑,需要严格校验仅支持指定媒体类型的话删除对应判断即可 - 校验逻辑自动兼容Accept头的质量值(q参数),权重为0的媒体类型会被直接判定为不匹配
内容的提问来源于stack exchange,提问作者Daniel Medrano
相关产品推荐
相关产品推荐

