ASP.NET MVC Core 3.0-为什么来自正文的API请求始终返回!ModelState.IsValid? [英] ASP.NET MVC Core 3.0 - Why API Request from body keeps returning !ModelState.IsValid?

查看:135
本文介绍了ASP.NET MVC Core 3.0-为什么来自正文的API请求始终返回!ModelState.IsValid?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在使用 ASP.NET MVC Core 3.0 创建一个API项目.我成功发送了不带参数的POST请求.但是目前,当尝试通过Postman发送带有JSON中的参数的POST请求时遇到问题,总是收到无效的请求,如下所示.

I'm currently using ASP.NET MVC Core 3.0 to create an API project. I was successful to send a POST request without parameter. But currently I'm having a problem when trying to send a POST request with the parameter in JSON via Postman, always getting invalid request as shown below.

请注意,查询字符串中还有 key 参数,可以使用我创建的中间件对请求进行授权.这部分没有问题.

Notice that there's also key param in the query string to authorize the request using the middleware I created. This part has no problem.

这是控制器的代码:

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // POST api/values
    [HttpPost]
    public IActionResult Post([FromBody] UserRequest model)
    {
        if (!ModelState.IsValid)
            return BadRequest(new ApiResponse(400, "Model state is not valid."));

        return Ok($"Hello world, {model.Id}!");
    }
}

奇怪的是,我已经创建并将UserRequest类用作参数输入,如下所示:

The odd thing is, I've already created and used the class UserRequest as a parameter input, as shown below:

public class UserRequest
{
    public string Id { get; set; }
}

这是我的 Startup.cs 设置,我已经添加了 AddNewtonsoftJson 来启用JSON序列化器输入:

Here's my Startup.cs settings, I've already added AddNewtonsoftJson to enable JSON serializer input:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(option => option.EnableEndpointRouting = false)
        .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
        .AddNewtonsoftJson(opt => opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);

    /*Other API, DB settings and services goes here*/
    ...
}

到目前为止,这是我的尝试:

Here's my attempts so far:

  1. UserRequest 类上添加了 [BindProperties] .仍然返回相同的错误.
  2. 删除了控制器参数上的 [FromBody] .仍然返回相同的错误.
  3. id 重命名为 Id ,以遵循 UserRequest 类中的命名.仍然返回相同的错误.
  4. Startup.cs 上添加了此代码,这将执行 return BadRequest(new ApiResponse(400,模型状态无效".)); :

  1. Added [BindProperties] on UserRequest class. Still returning same error.
  2. Removed [FromBody] on the parameter of controller. Still returning same error.
  3. Renamed id to Id to follow the naming inside UserRequest class. Still returning same error.
  4. Added this code on Startup.cs, this will execute return BadRequest(new ApiResponse(400, "Model state is not valid."));:

.ConfigureApiBehaviorOptions(options =>
{
    options.SuppressModelStateInvalidFilter = true;
})

  • Startup.cs 上删除了此代码

    .AddNewtonsoftJson(opt => opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore)
    

    它将返回以下内容:

    {
        "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
        "title": "One or more validation errors occurred.",
        "status": 400,
        "traceId": "|f6037d12-44fa46ceaffd3dba.",
        "errors": {
            "$": [
                "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0."
            ]
        }
    }
    

  • 任何帮助将不胜感激.

    更新日期:2019年12月11日:这是我处理API密钥请求的方式:

    public async Task Invoke(HttpContext httpContext, IApiKeyService apiKeyService)
    {
        var remoteIpAddress = httpContext.Connection.RemoteIpAddress;
    
        if (httpContext.Request.Path.StartsWithSegments("/api"))
        {
            _logger.LogInformation($"Request from {remoteIpAddress}.");
    
            var queryString = httpContext.Request.Query;
            queryString.TryGetValue("key", out var keyValue);
    
            if (keyValue.ToString().Any(char.IsWhiteSpace))
                keyValue = keyValue.ToString().Replace(" ", "+");
    
            if (httpContext.Request.Method != "POST")
            {
                httpContext.Response.StatusCode = StatusCodes.Status405MethodNotAllowed;
                await WriteJsonResponseAsync(httpContext, "Only POST method is allowed.");
                return;
            }
    
            if (keyValue.Count == 0)
            {
                httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
                await WriteJsonResponseAsync(httpContext, "API Key is missing.");
                return;
            }
    
            var isKeyValid = await apiKeyService.IsApiKeyValidAsync(keyValue);
            var isKeyActive = await apiKeyService.IsApiKeyActiveAsync(keyValue);
    
            if (!isKeyValid)
            {
                httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
                await WriteJsonResponseAsync(httpContext, "Invalid API Key.");
                return;
            }
    
            if (!isKeyActive)
            {
                httpContext.Response.StatusCode = StatusCodes.Status406NotAcceptable;
                await WriteJsonResponseAsync(httpContext, "Service is Deactivated.");
                return;
            }
        }
        await _next.Invoke(httpContext);
    }
    
    private static async Task WriteJsonResponseAsync(HttpContext httpContext, string message = null)
    {
        httpContext.Response.ContentType = "application/json";
        var response = new ApiResponse(httpContext.Response.StatusCode, message);
        var json = JsonConvert.SerializeObject(response);
        await httpContext.Response.WriteAsync(json);
    }
    

    推荐答案

    如注释中所述,您的日志记录中间件正在引起此问题.读取请求正文或响应正文时,需要重置流,以便其他中间件可以读取它(在本例中为JsonSerializer).

    As discussed in the comments your logging middleware is causing the problem. When you read the request body, or response body, you need to reset the stream so that other middleware can read it (in this case the JsonSerializer).

    在您的日志记录中间件中,您将有一个呼叫,例如:

    In your logging middleware you will have a call like:

    var body = await new StreamReader(request.Body).ReadToEndAsync();
    

    在该方法返回之前,您需要重置该流:

    Before the method returns you need to reset that stream:

    request.Body.Seek(0, SeekOrigin.Begin);
    

    例如,对于响应代码,这是相同的.

    This is the same for the response code e.g.

    response.Body.Seek(0, SeekOrigin.Begin);
    

    编辑

    按照评论中的要求,这里是中间件代码可能是一个示例:

    As requested in the comments here is an example of what the middleware code might be:

    public class LoggingMiddleware
    {
        private readonly RequestDelegate _next;
    
        public LoggingMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        public async Task Invoke(HttpContext context)
        {
            context.Request.EnableBuffering();
            var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
    
            // Log the contents of body...
    
            context.Request.Body.Seek(0, SeekOrigin.Begin);
    
            await _next(context);
        }
    }
    

    重置 Body 流位置的代码需要在调用 _next(context)

    The code to reset the Body stream position needs to come before the call to _next(context)

    这篇关于ASP.NET MVC Core 3.0-为什么来自正文的API请求始终返回!ModelState.IsValid?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆