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

Web API合规请求管控:替换415状态码及异常返回逻辑优化咨询

嘿,我明白你想要实现的是一个极度严格的Web API——只接受完全符合指定顺序的请求(包括请求头排序、请求体字段排序),不合规的直接返回404。默认的ASP.NET Core绑定逻辑确实不会校验这些顺序,给你一套落地的解决方案:

一、校验请求头顺序

ASP.NET Core默认不会管请求头的顺序,毕竟HTTP标准里请求头是无序的,但你要硬卡顺序的话,写个中间件就能搞定:

1. 编写请求头校验中间件

public class RequestHeaderOrderMiddleware
{
    private readonly RequestDelegate _next;
    private readonly List<string> _requiredHeaderOrder;

    public RequestHeaderOrderMiddleware(RequestDelegate next, List<string> requiredOrder)
    {
        _next = next;
        // 统一转小写,避免大小写匹配问题
        _requiredHeaderOrder = requiredOrder.Select(h => h.ToLowerInvariant()).ToList();
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // 获取请求里实际的头键顺序(按服务器接收顺序)
        var incomingHeaders = context.Request.Headers.Select(h => h.Key.ToLowerInvariant()).ToList();
        
        // 对比是否和要求的顺序完全匹配
        var isValid = _requiredHeaderOrder.SequenceEqual(incomingHeaders.Take(_requiredHeaderOrder.Count));
        
        if (!isValid)
        {
            context.Response.StatusCode = 404;
            await context.Response.WriteAsync("Invalid request header sequence");
            return;
        }

        // 校验通过,继续处理请求
        await _next(context);
    }
}

// 给IApplicationBuilder加扩展方法,方便注册中间件
public static class HeaderOrderMiddlewareExtensions
{
    public static IApplicationBuilder UseHeaderOrderValidation(this IApplicationBuilder app, List<string> requiredOrder)
    {
        return app.UseMiddleware<RequestHeaderOrderMiddleware>(requiredOrder);
    }
}

2. 在Program.cs中注册中间件

记得把中间件放在路由之前,确保请求进来第一时间就被校验:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
var app = builder.Build();

// 替换成你要求的请求头顺序,比如["Content-Type", "Authorization", "X-App-Key"]
var requiredHeaders = new List<string> { "Content-Type", "Authorization" };
app.UseHeaderOrderValidation(requiredHeaders);

app.UseRouting();
app.UseAuthorization();

app.MapControllers();

app.Run();
二、校验请求体字段顺序

默认的[FromBody]绑定会直接忽略JSON字段的顺序(毕竟JSON规范里字段是无序的),所以得自定义输入格式化器来手动校验:

1. 自定义JSON输入格式化器

这个格式化器会先读取原始JSON,校验字段顺序,再交给默认逻辑解析:

public class OrderedJsonInputFormatter : SystemTextJsonInputFormatter
{
    private readonly List<string> _requiredPropertyOrder;

    public OrderedJsonInputFormatter(JsonOptions options, List<string> propOrder) : base(options)
    {
        _requiredPropertyOrder = propOrder;
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
    {
        // 读取原始请求体字符串
        using var reader = new StreamReader(context.HttpContext.Request.Body, encoding);
        var rawJson = await reader.ReadToEndAsync();
        
        // 解析JSON,获取实际的字段顺序
        var jsonDoc = JsonDocument.Parse(rawJson);
        var incomingProps = jsonDoc.RootElement.EnumerateObject().Select(p => p.Name).ToList();
        
        // 对比顺序是否符合要求
        var isValid = _requiredPropertyOrder.SequenceEqual(incomingProps.Take(_requiredPropertyOrder.Count));
        
        if (!isValid)
        {
            context.ModelState.AddModelError(string.Empty, "Invalid request body property sequence");
            return await InputFormatterResult.FailureAsync(context.ModelState);
        }
        
        // 重置请求流(因为已经读过一次了,得让默认解析逻辑能重新读取)
        context.HttpContext.Request.Body = new MemoryStream(encoding.GetBytes(rawJson));
        
        // 交给默认的JSON解析逻辑处理
        return await base.ReadRequestBodyAsync(context, encoding);
    }
}

// 扩展方法方便注册格式化器
public static class OrderedJsonFormatterExtensions
{
    public static IMvcBuilder AddOrderedJsonFormatter(this IMvcBuilder builder, List<string> propOrder)
    {
        builder.AddMvcOptions(options =>
        {
            // 替换默认的SystemTextJsonInputFormatter
            var defaultFormatter = options.InputFormatters.OfType<SystemTextJsonInputFormatter>().FirstOrDefault();
            if (defaultFormatter != null)
            {
                options.InputFormatters.Remove(defaultFormatter);
                options.InputFormatters.Add(new OrderedJsonInputFormatter(options.JsonOptions, propOrder));
            }
        });
        return builder;
    }
}

2. 注册格式化器

全局生效(所有控制器)

// Program.cs
builder.Services.AddControllers()
    // 替换成你要求的请求体字段顺序,比如["UserId", "UserName", "Email"]
    .AddOrderedJsonFormatter(new List<string> { "Id", "Name", "Description" });

仅特定控制器/方法生效

如果只需要给某个接口做校验,可以创建针对特定模型的格式化器,或者在控制器上用[Consumes]指定(全局注册更简单直接)。

三、统一返回404

上面的中间件已经在头校验失败时返回404了,请求体校验失败时可以加个全局过滤器统一返回404:

public class ValidationTo404Filter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new NotFoundResult();
        }
    }

    public void OnActionExecuted(ActionExecutedContext context) { }
}

// 在Program.cs注册过滤器
builder.Services.AddControllers(options =>
{
    options.Filters.Add<ValidationTo404Filter>();
});
一些重要提醒
  • HTTP标准里请求头是无序的,强制校验顺序可能会导致一些合法请求被拦截(比如某些代理服务器会重排请求头),一定要确认业务真的需要这么严格的限制。
  • JSON规范里字段也是无序的,硬卡顺序不符合JSON的设计初衷,建议再评估下必要性。
  • 自定义格式化器时记得重置请求流,不然默认解析逻辑会读不到数据。

内容的提问来源于stack exchange,提问作者nevsnirG

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 03:54:28