为何MissingMemberHandling对请求Header无效?如何全局检测额外参数
问题描述
需求:当请求Header或Body传入额外/未知参数时返回400状态码,使用Newtonsoft序列化并设置MissingMemberHandling.Error。通过[FromHeader]和[FromBody]绑定API参数后,Body传入额外参数会报错,但Header传入额外参数会被忽略。
现有配置与代码
Startup.cs 配置
services.AddControllers() .AddNewtonsoftJson(options => { options.SerializerSettings.Converters.Add(new StringEnumConverter()); options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error; })
控制器API代码
public async Task<ActionResult<Response>> TestAPI([FromHeader] RequestHeader header, [FromBody] RequestBody body) { // 业务逻辑实现 } public class RequestHeader { [FromHeader(Name = "x-sessionId")] public string SessionId { get; set; } } public class RequestBody { // 注意:此处[FromBody]是错误用法,应改为[JsonProperty("id")],[FromBody]仅用于控制器参数绑定 [FromBody(Name = "id")] public string Id { get; set; } }
当前现象:Body传入id以外的参数时,Newtonsoft会触发错误返回400;但Header传入未定义的参数时,会被直接忽略,不会返回错误。
疑问:
- 为何
MissingMemberHandling对Header无效? - 如何全局检测请求Header或Query中的额外参数并返回400错误?
解答
一、MissingMemberHandling对Header无效的原因
MissingMemberHandling.Error是Newtonsoft.Json的序列化/反序列化配置,仅作用于JSON格式请求体(Body)的反序列化过程:
- Body参数是将整个JSON字符串反序列化为目标模型类,此时Newtonsoft会遍历JSON字段与模型属性匹配,发现未知字段就抛出错误。
- Header参数的绑定由ASP.NET Core的模型绑定系统负责,并非通过Newtonsoft处理。模型绑定会逐个提取请求头中的键值对,映射到模型类标记了
[FromHeader]的属性,未匹配的Header会直接被忽略,不会触发Newtonsoft的校验逻辑。
二、全局检测Header/Query额外参数的方案
要实现全局校验Header或Query的未知参数,可通过以下两种方式实现:
方案1:自定义Header模型绑定器(针对单个Header模型)
为RequestHeader这类Header模型自定义绑定器,在绑定过程中校验所有请求头是否都能匹配到模型属性:
public class StrictHeaderModelBinder : IModelBinder { public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); var modelType = bindingContext.ModelType; // 收集模型中所有允许的Header名称 var allowedHeaders = modelType.GetProperties() .Where(p => p.GetCustomAttributes(typeof(FromHeaderAttribute), false).Any()) .Select(p => { var attr = p.GetCustomAttribute<FromHeaderAttribute>(); return attr?.Name ?? p.Name; }) .ToHashSet(StringComparer.OrdinalIgnoreCase); // 找出所有未匹配的请求头 var extraHeaders = bindingContext.HttpContext.Request.Headers.Keys .Where(h => !allowedHeaders.Contains(h)) .ToList(); if (extraHeaders.Any()) { bindingContext.ModelState.AddModelError("", $"存在未知请求头:{string.Join(", ", extraHeaders)}"); return Task.CompletedTask; } // 执行默认绑定逻辑完成模型赋值 var defaultBinder = new SimpleTypeModelBinder(modelType); return defaultBinder.BindModelAsync(bindingContext); } }
然后在RequestHeader类上标记使用该绑定器:
[ModelBinder(BinderType = typeof(StrictHeaderModelBinder))] public class RequestHeader { [FromHeader(Name = "x-sessionId")] public string SessionId { get; set; } }
方案2:全局中间件(检测所有请求的Header/Query)
如果需要对所有请求的Header和Query进行全局校验,可编写中间件实现:
public class StrictParametersMiddleware { private readonly RequestDelegate _next; public StrictParametersMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { var endpoint = context.GetEndpoint(); if (endpoint != null) { var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>(); if (actionDescriptor != null) { var allowedHeaders = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var allowedQueries = new HashSet<string>(StringComparer.OrdinalIgnoreCase); // 收集当前Action允许的所有Header和Query参数名 foreach (var parameter in actionDescriptor.Parameters) { // 处理Header类型参数 if (parameter.BindingInfo?.BindingSource == BindingSource.Header) { var props = parameter.ParameterType.GetProperties() .Where(p => p.GetCustomAttributes(typeof(FromHeaderAttribute), false).Any()); foreach (var prop in props) { var attr = prop.GetCustomAttribute<FromHeaderAttribute>(); allowedHeaders.Add(attr?.Name ?? prop.Name); } } // 处理Query类型参数 else if (parameter.BindingInfo?.BindingSource == BindingSource.Query) { // 处理复杂模型类型的Query参数 var props = parameter.ParameterType.GetProperties() .Where(p => p.GetCustomAttributes(typeof(FromQueryAttribute), false).Any()); foreach (var prop in props) { var attr = prop.GetCustomAttribute<FromQueryAttribute>(); allowedQueries.Add(attr?.Name ?? prop.Name); } // 处理简单类型的Query参数(如string、int) if (parameter.ParameterType.IsValueType || parameter.ParameterType == typeof(string)) { var attr = parameter.GetCustomAttribute<FromQueryAttribute>(); allowedQueries.Add(attr?.Name ?? parameter.Name); } } } // 校验未知Header var extraHeaders = context.Request.Headers.Keys.Where(h => !allowedHeaders.Contains(h)).ToList(); if (extraHeaders.Any()) { context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsync($"存在未知请求头:{string.Join(", ", extraHeaders)}"); return; } // 校验未知Query var extraQueries = context.Request.Query.Keys.Where(q => !allowedQueries.Contains(q)).ToList(); if (extraQueries.Any()) { context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsync($"存在未知查询参数:{string.Join(", ", extraQueries)}"); return; } } } await _next(context); } }
最后在Startup.cs中注册该中间件(需放在路由中间件之前):
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 其他中间件(如异常处理、静态文件)... app.UseMiddleware<StrictParametersMiddleware>(); app.UseRouting(); // 其他中间件(如授权、端点路由)... }
内容的提问来源于stack exchange,提问作者Sabina
相关产品推荐
相关产品推荐

