【问题标题】:How to discern between model binding errors and model validation errors?如何区分模型绑定错误和模型验证错误?
【发布时间】:2018-02-07 00:52:42
【问题描述】:

我正在使用 ASP.NET Core MVC 2.0 实现一个 REST API 项目,如果模型绑定失败(因为请求在语法上错误)我想返回 400 状态代码,如果模型验证返回 422 状态代码失败(因为请求在语法上是正确的,但包含不可接受的值)。

举个例子,给定一个动作

[HttpPut("{id}")]
public async Task<IActionResult> UpdateAsync(
    [FromRoute] int id,
    [FromBody] ThingModel model)

当路由中的 id 参数包含非数字字符或请求中未指定正文时,我想返回 400 状态码,当 ThingModel 的属性时返回 422 状态码包含无效值。

据我所知,IValueProviderIModelBinder 实现都像验证器一样将它们的错误添加到请求的 ModelStateDictionary 中,并且无法在绑定和验证之间注入代码。

如何实现这样的行为?

【问题讨论】:

    标签: c# asp.net-core-mvc model-binding model-validation


    【解决方案1】:

    尚未检查选项 #1,但是:

    • ModelState.ValidationState 有 4 个可能的值(未验证、无效、有效、已跳过),如果出现 模型绑定错误,我会得到 未验证作为一个值
    • 还会考虑使用 ApiBehaviorOptions(参见示例 here)自动返回 new BadRequestObjectResult(actionContext.ModelState) - 因为在绑定错误的情况下使用 NULL绑定值没有什么可做的,如果出现验证错误,我们可能也无能为力。

    ApiBehaviorOptions 快速说明:

    • 必须使用 ApiController 属性(它还需要控制器级别的路由属性,并且确实会改变绑定的工作方式)
    • 具有 ApiController 属性的默认行为将返回 BadRequestObjectResult(actionContext.ModelState),无需任何额外代码和配置
    • 如果您决定推出自己的 ApiBehaviorOptions - 您必须在 service.AddMvc 之后对其进行初始化或需要使用:services.PostConfigure(o => {}) 具有类似的效果
    • ApiBehaviorOptions SuppressModelStateInvalidFilter 必须为 false 才能使 InvalidModelStateResponseFactory 工作

    因此在某些情况下,自定义过滤器是更好的解决方案(更改更少)。

    【讨论】:

    • 涉及:HttpPost FromBody 属性未绑定(为空) - 请参阅:stackoverflow.com/questions/24129919/…
    • 感谢“您必须在 service.AddMvc 之后对其进行初始化”。
    • 第一个要点不会有任何好处。以下情况均返回值Invalid:a) 没有正文,b) 模型绑定失败(由于数据类型无效)和 c) 模型验证失败(语法正常,但数据未通过验证标准)。
    • 你可以选择ModelState.Root.ValidationState,但这仍然不够精确,因为你会得到值Invalid/Unvalidated/Unvalidated,所以你无法辨别情况b)和c)来自上面的评论。
    【解决方案2】:

    在您的项目中添加以下属性

    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            base.OnActionExecuting(context);
    
            if (!context.ModelState.IsValid)
                context.Result = new BadRequestObjectResult(new
                {
                    message = context.ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage))
                });
        }
    }
    

    像这样改变你的 api

    [HttpPut("{id}")]
    [ValidateModel]
    public async Task<IActionResult> UpdateAsync(
        [FromRoute] int id,
        [FromBody] ThingModel model)
    

    id 包含一个非数字 => 400

    model 内部没有通过注解验证 => 400

    如果您想使用 422 代码拒绝模型中不可接受的值,请在您的控制器中实现

    【讨论】:

    • 除非我做错了什么,否则当执行过滤器/属性时,模型验证已经发生,您在 ModelState 中已经存在绑定错误和验证错误。
    猜你喜欢
    • 2013-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    相关资源
    最近更新 更多