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

.NET7中ActionFilterAttribute获取HttpContext.Request.Body始终为空的问题

在.NET7的ActionFilterAttribute中正确获取请求体并检查属性拼写错误

一、确保请求缓冲中间件配置正确

请求体默认只能被读取一次,必须在路由管道前启用请求缓冲,且中间件顺序不能错。以下是Program.cs的正确配置:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        // 可选:根据需求配置大小写敏感性,方便后续属性校验
        options.JsonSerializerOptions.PropertyNameCaseInsensitive = false;
    });

var app = builder.Build();

// 启用请求缓冲,必须放在UseRouting之前
app.Use(async (context, next) =>
{
    context.Request.EnableBuffering();
    await next();
});

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

app.Run();

二、修改ActionFilterAttribute实现正确读取与校验

核心是读取请求体后重置流位置,保证后续模型绑定正常工作,同时对比模型属性检查拼写错误:

using Microsoft.AspNetCore.Mvc.Filters;
using System.Reflection;
using System.Text;
using System.Text.Json;

public class ValidateRequestBodyAttribute : ActionFilterAttribute
{
    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var request = context.HttpContext.Request;

        if (request.ContentType?.Contains("application/json") == true && request.Body.CanSeek)
        {
            // 重置流到起始位置,避免因其他组件提前读取导致空内容
            request.Body.Position = 0;
            
            using var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true);
            var requestBody = await reader.ReadToEndAsync();
            
            // 重置流位置,确保后续模型绑定能正常读取
            request.Body.Position = 0;

            if (!string.IsNullOrEmpty(requestBody))
            {
                await ValidateUndefinedProperties(requestBody, context);
            }
        }

        await next();
    }

    private async Task ValidateUndefinedProperties(string requestBody, ActionExecutingContext context)
    {
        // 解析原始JSON,保留所有传入属性
        using var jsonDoc = JsonDocument.Parse(requestBody);
        var rootObj = jsonDoc.RootElement;

        // 获取ScoreModel的所有属性名称
        var validProperties = typeof(ScoreModel).GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Select(p => p.Name)
            .ToHashSet(StringComparer.OrdinalIgnoreCase); // 忽略大小写,可根据需求调整

        // 遍历所有传入属性,校验是否存在于模型中
        foreach (var prop in rootObj.EnumerateObject())
        {
            if (!validProperties.Contains(prop.Name))
            {
                context.ModelState.AddModelError(prop.Name, $"属性 '{prop.Name}' 不存在或拼写错误");
            }
        }

        // 若存在错误,直接返回BadRequest,终止后续Action执行
        if (!context.ModelState.IsValid)
        {
            context.Result = new Microsoft.AspNetCore.Mvc.BadRequestObjectResult(context.ModelState);
        }
    }
}

三、ScoreModel示例

public class ScoreModel
{
    public int Id { get; set; }
    public string StudentName { get; set; }
    public decimal MathScore { get; set; }
    public decimal EnglishScore { get; set; }
}

四、控制器中使用过滤器

[ApiController]
[Route("api/scores")]
public class ScoresController : ControllerBase
{
    [HttpPost]
    [ValidateRequestBody]
    public IActionResult SubmitScore(ScoreModel model)
    {
        // 正常业务逻辑处理
        return Ok(model);
    }
}

常见问题排查

  • 确认缓冲中间件在UseRouting之前配置,否则缓冲不生效
  • 检查请求的ContentType是否为application/json,非JSON请求需调整过滤器中的判断逻辑
  • 确保request.Body.CanSeek返回true,若为false则说明缓冲未正确启用
  • 若有其他中间件(如日志中间件)提前读取了请求体,需确保它们读取后也重置了流位置

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 05:22:25