ASP.NET MVC Core 3.0 - Why API Request from body keeps returning !ModelState.IsValid?

好久不见. 提交于 2020-04-30 04:43:45

问题


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.

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.

Here's the code of the controller:

[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}!");
    }
}

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; }
}

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. 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;
    })
    
  5. Removed this code on Startup.cs

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

    It will return this instead:

    {
        "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."
            ]
        }
    }
    

Any help would be greatly appreciated.

Updated 12/11/2019: Here's how I handle the API key request:

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);
}

回答1:


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);


来源:https://stackoverflow.com/questions/58812020/asp-net-mvc-core-3-0-why-api-request-from-body-keeps-returning-modelstate-isv

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!