为啥 ModelState 返回不同的结果以及如何修复它?

Posted

技术标签:

【中文标题】为啥 ModelState 返回不同的结果以及如何修复它?【英文标题】:Why ModelState returns different result and how to fix it?为什么 ModelState 返回不同的结果以及如何修复它? 【发布时间】:2021-06-19 02:06:05 【问题描述】:

Asp.net 核心 3.1 WebApi。

我有一个具有所需属性的模型。

1.如果模型无效,则响应包含数据 喜欢:


    "errors": 
        "Name": [
            "Update model can't have all properties as null."
        ],
        "Color": [
            "Update model can't have all properties as null."
        ]
    ,
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|f032f1c9-4c36d1e62aa60ead."

这对我来说看起来不错。

但是如果我向 modelState.AddModelError("statusId", "Invalid order status id.") 添加一些自定义验证,那么它会返回不同的结构:

[
    
        "childNodes": null,
        "children": null,
        "key": "statusId",
        "subKey": 
            "buffer": "statusId",
            "offset": 0,
            "length": 8,
            "value": "statusId",
            "hasValue": true
        ,
        "isContainerNode": false,
        "rawValue": "11202",
        "attemptedValue": "11202",
        "errors": [
            
                "exception": null,
                "errorMessage": "Invalid order status id."
            
        ],
        "validationState": 1
    
]

看起来 ModelState.IsValid 实际上不再适用于控制器,因为错误的请求甚至在进入无效模式的控制器之前就返回了。还是有一些通过 ModelSate 进行全局验证的标志?

为什么结构不同? 如何让它一样? 如何在 MVC 中强制访问 api 控制器内部的 ModelState.IsValid 方法?

更新:



    [Route("....")]
    [Authorize]
    [ApiController]
    public class StatusesController : ApiControllerBase
    


        [HttpPut, Route("statusId")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status409Conflict)]
        [Produces("application/json")]
        public async Task<ObjectResult> UpdateStatusAsync(int statusId, [FromBody] StatusUpdateDto orderStatusUpdateDto)
        

            int companyId = User.Identity.GetClaimValue<int>(ClaimTypes.CompanyId);

            const string errorNotFound  = "There is not order status with this id for such company";
            if (statusId <= 0)
            
                Logger.LogError(errorNotFound);
                ModelState.AddErrorModel(nameof(statusId), "Invalid order status id")
                throw new NotFound(ModelState);
            
            
            if (orderStatusUpdateDto == null)
            
                const string error = "Invalid (null) order status can't be added";
                Logger.LogError(error);
                throw new ArgumentNullException(error);
            

            if (ModelState.IsValid == false) // also this code is always true or returns 400 before this line
            
                return BadRequest(ModelState); 
            

           ....
            return result;
        


【问题讨论】:

您在哪里将错误添加到模型状态?在控制器方法里面?你用 ApiController 属性装饰控制器吗? 在控制器方法内部 - 是的,用 ApiController 装饰控制器 - 是的 除了下面treze的回答外,您还可以使用.ConfigureApiBehaviorOptions(o =&gt; o.InvalidModelStateResponseFactory = ...)配置用于产生无效模型结果的工厂。 【参考方案1】:

ApiController 属性为控制器添加了一些特定的自以为是的行为。其中之一是如果模型无效,则返回 400 错误。 可以禁用此行为,但只能在全局级别上禁用。

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    
        options.SuppressModelStateInvalidFilter = true;
    );

我认为您有以下选择:

禁用此行为并自己检查ModelState.IsValid。使用ValidationProblem 方法产生相同的响应 将此检查添加到模型的验证器中 保持一切原样。但是在控制器方法中使用ValidationProblem 会返回验证错误。

有关信息,请参阅https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#automatic-http-400-responses

【讨论】:

谢谢。这回答了我的一些问题。您如何看待返回结果中的不同结构? 添加自定义错误时,ActionResult 会返回什么?如果您返回 ValidationProblem(),您应该得到相同的响应。 docs.microsoft.com/en-us/dotnet/api/… 我已经修复了示例 buf 我返回 BadRequest 或 NotFound(如果 id 小于 0)并传递 ModelState 以导致两种情况 哦……好像是这个原因!一旦我返回 NotFound - 我得到不同的结果结构,然后我返回 BadRequest(预期结构)。也许验证工厂对错误请求有特定的实现,而对其他请求则有默认实现...... 是的,正如我在答案“为了使自动和自定义响应保持一致,请调用 ValidationProblem 方法而不是 BadRequest。ValidationProblem 返回 ValidationProblemDetails 对象以及自动响应”中链接的文档中所述。

以上是关于为啥 ModelState 返回不同的结果以及如何修复它?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 MVC Web API 发布方法中将 ModelState 错误返回到 Kendo 网格?

ASPNET MVC - 当该字段确实有值时,为啥 ModelState.IsValid false“需要 x 字段”?

如何在 UseExceptionHandler 中访问 ModelState

为啥我的 MVC 应用程序中的 ModelState.IsValid 属性在我看来一切正常时等于 false

将 ModelState 转换为 JSON 以进行日志记录

为啥 getExternalFilesDirs 根据使用的上下文返回不同的结果?