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

Reynaldi

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.

enter image description here

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

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

EDIT

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

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

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Ajax request body from view to controller is empty in ASP NET Core MVC 3

Asp Net Core Web Api POST Request.Body always length 0

asp.net core 1.0 mvc. Get raw content from Request.Body

Unable to read request body from HttpContext on asp.net core api deployed on Lambda

c# asp.net core 3 calling different methods from the controller, depending on the request body

Returning JSON Errors from Asp.Net MVC 6 API

Read raw Request.Body in Asp.Net Core MVC Action with Route Parameters

.Net Core API Returning StreamContent with request object

ASP.NET core MVC+Razor POST controller returning "bad request"

Asp.net core 3 Web Api post request not working

How to properly map array from body returning zero count in asp.net core

Request body from is empty in .Net Core 3.0

Why is this ASP.NET Core POST Request Returning "405 (Method Not Allowed)"?

How to submit valid XML in a POST request body to an ASP.NET Core Web API that consumes XML?

Why is my .Net Core API not receiving data from request payload?

ASP.NET Core MVC table keeps showing same number

Posting request from javascript in AJAX to C# methods, no returning value, ASP.NET, MVC

ASP.NET Web Api 2 post request From Uri and From Body

Route to ASP.NET Core MVC controller from AJAX returning 404

Bind an object to asp mvc controller from ajax request using asp.net core and Jquery

SessionID keeps changing in ASP.NET Core web api

Posting a file from ASP.NET MVC 4.6 application to ASP.NET Core Web API

How to make PUT from ASP.NET MVC to ASP.NET Core Web API?

JsonIgnore attribute keeps serializing properties in ASP.NET Core 3

Re-read a Request body in ASP.NET core

ASP.NET Core modify/substitute request body

ASP.NET Core manually dispose request body stream

How to change request body in IActionFilter in asp.net core

Replacing Request/Response Body in asp.net core middleware

ASP.NET Core MVC - Why does Ajax JSON request return 400 on POST instead of 403