c# - ASP.NET MVC Core 3.0 - 为什么来自主体的 API 请求不断返回!ModelState.IsValid?

标签 c# asp.net-core asp.net-web-api

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

enter image description here

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

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

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

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

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

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*/
    ...
}

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

  1. UserRequest 类上添加了 [BindProperties]。仍然返回相同的错误。
  2. 删除了 Controller 参数上的[FromBody]。仍然返回相同的错误。
  3. id 重命名为 Id 以遵循 UserRequest 类中的命名。仍然返回相同的错误。
  4. Startup.cs 上添加了这段代码,这将执行 return BadRequest(new ApiResponse(400, "Model state is not valid.")); :

    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressModelStateInvalidFilter = true;
    })
    
  5. 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."
            ]
        }
    }
    

如有任何帮助,我们将不胜感激。

12/11/2019 更新:以下是我处理 API key 请求的方式:

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

在您的日志记录中间件中,您将进行如下调用:

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

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

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

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

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

编辑

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

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) 之前出现

关于c# - ASP.NET MVC Core 3.0 - 为什么来自主体的 API 请求不断返回!ModelState.IsValid?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58812020/

相关文章:

HttpClient Post REST API 中的 C# 多部分表单数据

c# - Unity 无法打开二进制文件

c# - 如何使用 CURL 或 ARC 向受 Azure AD 授权保护的 Web Api 发出请求

c# - DelegatingHandler 适用于本地主机,但不适用于 Azure

c# - 根据自定义属性 UWP 禁用某些 ListViewItem

c# - Windows Phone,你能从GPS获得实时时间吗?

c# - 以 asp-for 作为参数的自定义 ViewComponent

iis - .NET Core SDK未安装ASPNETCoreModule

msbuild - 使用 .NET Core csproj VS 2017 工具最小化引用

c# - Web api 身份验证和 MVC 4