Web API 验证不使用自定义模型绑定器触发

Posted

技术标签:

【中文标题】Web API 验证不使用自定义模型绑定器触发【英文标题】:Web API Validation not trigger with custom model binder 【发布时间】:2017-04-19 12:35:44 【问题描述】:

我正在使用 Web API 5 构建 Web 服务。我正在通过扩展 IModelBinder 接口以将复杂类型作为参数映射到操作来实现自定义模型绑定器。绑定部分工作正常。但是模型验证不会发生。 ModelState.IsValid 始终为真。

public class PagingParamsVM

        [Range(1, Int32.MaxValue, ErrorMessage = "Page must be at least 1")]
        public int? Page  get; set; 

        [Range(1, Int32.MaxValue, ErrorMessage = "Page size must be at least 1")]
        public int? PageSize  get; set; 


public class PaginationModelBinder : IModelBinder

        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        
              var model = (PagingParamsVM)bindingContext.Model ?? new PagingParamsVM();
              //model population logic
              .....

              bindingContext.Model = model;
              return true;
        


public IEnumerable<NewsItemVM> Get([ModelBinder(typeof(PaginationModelBinder))]PagingParamsVM pegination)

            //Validate(pegination); //if I call this explicitly ModelState.IsValid is set correctly.
            var valid = ModelState.IsValid; //this is always true


public class ModelStateValidationActionFilter : ActionFilterAttribute

        public override void OnActionExecuting(HttpActionContext actionContext)
        
            var valid = actionContext.ModelState.IsValid //this is always true.
        

如果我显式调用 Validate() 或使用 [FromUri] 属性,则 ModelState.IsValid 设置正确。

public IEnumerable<NewsItemVM> Get([FromUri]PagingParamsVM pegination)

            var valid = ModelState.IsValid;

我应该在模型绑定器中实现验证部分吗?如果是这样我应该如何实施?

【问题讨论】:

SO answer 可能重复。 @MihailStancescu 我看到了这个问题。它适用于 DataAnnotations。但如果我使用 FluentValidation 或类似的,它就行不通了。所以这里似乎缺少一些东西。 【参考方案1】:

DefaultModelBinder.CreateModel 应该可以帮助您保持模型状态:

public class PaginationModelBinder : DefaultModelBinder

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    
        if(modelType == typeof(PagingParamsVM))
        
            var page = default(int?);
            var model = bindingContext.Model;
            var valueProvider = bindingContext.ValueProvider;
            var pageValue = valueProvider.GetValue("Page");
            var tmp = default(int);
            if(pageValue != null && int.TryParse(pageValue.AttemptedValue, out tmp))
            
                page = tmp;
            

            var pageSize = default(int?);
            var sizeValue = valueProvider.GetValue("PageSize");
            if(sizeValue != null && int.TryParse(sizeValue.AttemptedValue, out tmp))
            
                pageSize = tmp;
            
            return new PagingParamsVM  Page = page, PageSize = pageSize ;
        
        return base.CreateModel(controllerContext, bindingContext, modelType);
    

使用 binder 的 web api 控制器可以是:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;

public class NewsItemController : ApiController

    public IEnumerable<NewsItemVM> Get([ModelBinder(typeof(PaginationModelBinder))]PagingParamsVM pegination)
    
        //Validate(pegination); //if I call this explicitly ModelState.IsValid is set correctly.
        var valid = ModelState.IsValid; //this is always true
        return Enumerable.Empty<NewsItemVM>();
    

【讨论】:

Web API 没有 DefaultModelBinder。 DefaultModelBinder 带有 MVC。 @sajith,当您在 MVC .NET 应用程序中开发 Web API 时,DefaultModelBinder 将会出现:) 是的,我知道。但问题是你不能在控制器扩展 ApiController 中使用你的 PaginationModelBinder : DefaultModelBinder。 你试过你的代码了吗?我之前试过这个。它给出了以下错误,无法从“PaginationModelBinder”创建“IModelBinder”。请确保它派生自“IModelBinder”并具有公共无参数构造函数。我认为原因是模型绑定器必须从 System.Web.Http.ModelBinding.IModelBinder 派生。但是 DefaultModelBinder 在 System.Web.Mvc 命名空间中。【参考方案2】:

我找到了答案。可以在自定义模型绑定器中调用默认验证过程,如下所示,

public abstract class PaginationModelBinder : IModelBinder

        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        
              var model = (PagingParamsVM)bindingContext.Model ?? new PagingParamsVM();
              //model population logic
              .....

              bindingContext.Model = model;

              //following lines invoke default validation on model
              bindingContext.ValidationNode.ValidateAllProperties = true;
              bindingContext.ValidationNode.Validate(actionContext);

              return true;
        

感谢大家的支持。

【讨论】:

@Sajith 我也试过了,但是模型中使用的子实体没有触发验证。为主模型中使用的实体调用验证的任何选项 @MagendranV 我很好奇你是否已经设法找到解决方案,因为我遇到了同样的死胡同。 DataAnnotations 验证对于 1 级属性非常有效,但是任何更深的东西都不起作用

以上是关于Web API 验证不使用自定义模型绑定器触发的主要内容,如果未能解决你的问题,请参考以下文章

使用自定义模型绑定器的递归模型绑定

.NET Core 自定义模型绑定器调用默认模型绑定器

将自定义模型绑定器应用于 asp.net 核心中的对象属性

属性的自定义配置绑定器

P23 自定义Model绑定器

没有绑定器的 RxSwift 自定义数据类型转换