You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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传入未定义的参数时,会被直接忽略,不会返回错误。

疑问:

  1. 为何MissingMemberHandling对Header无效?
  2. 如何全局检测请求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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 06:55:30