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:
[BindProperties]
on UserRequest
class. Still returning same error.[FromBody]
on the parameter of controller. Still returning same error.id
to Id
to follow the naming inside UserRequest
class. Still returning same error.Added this code on Startup.cs
, this will execute return BadRequest(new ApiResponse(400, "Model state is not valid."));
:
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
})
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);
}
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.
Comments