c# - ASP.NET MVC Core 3.0 - Why API Request from body keeps returning !ModelState.IsValid?
问题描述
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:
- Added
[BindProperties]
onUserRequest
class. Still returning same error. - Removed
[FromBody]
on the parameter of controller. Still returning same error. - Renamed
id
toId
to follow the naming insideUserRequest
class. Still returning same error. Added this code on
Startup.cs
, this will executereturn 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)
推荐阅读
- python - pandas:如何检查列值是否在同一行的其他列中
- python - 弃用警告:不推荐使用计数。请改用 Collection.count_documents
- python - 隐蔽的ndarray列出
- android - Android Webview 在不活动后恢复时抛出“err_SSL_VERSION_INTERFERENCE”
- angular - 弹出菜单/在 Angular 中显示新值
- docker - 在 kubernetes 集群上使用 docker 命令运行 Jenkins 作业失败“docker:未找到”
- mongodb - 在 MongoDB 中将嵌入文档与原始文档分离
- python - 使用相同的代码,python时间和日期时间模块,时差同时正确和错误
- sql - Oracle Apex 查询需要比较两个表中不同时间的两个属性
- flutter - 基于ProxyProvider扩展架构的更好方法是什么?