• ASP.NET Core如何禁用模型验证(或者从模型状态中移除某些属性)?


     

    这是一篇4年前的文章:【经验分享】在ASP.NET Core中,如果禁用某个请求的模型验证?

    事隔多年,又有网友问到这个问题。我就来重新整理一下,顺便扩展一下之前的解决办法。

     

    =====

    这是一个来自网友【David】的提问。在 AppBoxCore 项目的新增用户页面,新增一个上传按钮:

    1
    2
    3
    4
    5
    "filePhoto" ShowRedStar="false" ShowEmptyLabel="true" ButtonText="上传个人头像"
    ButtonOnly="true" Required="false" ButtonIcon="ImageAdd"
    OnFileSelected="@Url.Handler("filePhoto_FileSelected")"
    OnFileSelectedFields="filePhoto">

      

    后台代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public IActionResult OnPostFilePhoto_FileSelected(IFormFile filePhoto, IFormCollection values)
    {
        if (filePhoto != null)
        {
            string fileName = filePhoto.FileName;
     
            // 。。。
        }
     
        return UIHelper.Result();
    }

      

    此时上传照片时,会弹出错误提示框(截图所示)。

    The 用户名 field is required.
    The 邮箱 field is required.
    The 性别 field is required.
    The 密码 field is required.

     

    这是因为页面模型中定义了一个绑定属性:

    1
    2
    3
    4
    5
    6
    7
    8
    [CheckPower(Name = "CoreUserNew")]
    public class UserNewModel : BaseAdminModel
    {
        [BindProperty]
        public User CurrentUser { get; set; }
     
        // ...
    }

      

    而在 POST 请求中会触发模型绑定,如果发现模型不完整就会报错。这个错误提示框是由 FineUICore 框架处理的,无需自己写代码。

    现在问题就来了!

    ====================
    对于上述场景,仅仅是上传图片而无需验证 CurrentUser 模型属性,该如何处理呢?

    其实也很简单,只需要在处理器中清空模型状态就可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public IActionResult OnPostFilePhoto_FileSelected(IFormFile filePhoto, IFormCollection values)
    {
        ModelState.Clear();
     
        if (filePhoto != null)
        {
            string fileName = filePhoto.FileName;
     
            // 。。。
        }
     
        return UIHelper.Result();
    }

      

     

    Done!

    ====================
    这个问题也确实让我走了不少弯路,刚开始总想着如何禁用某些POST请求的模型验证,想着微软总能为处理器(Handler)提供一些注解(Annotation)来吧,结果查了一圈没有发现!

    后来,想着换个思路:既然找不到禁用的方法,就清空模型状态,结果也是一样的。

     

     

     

    延伸思考
    ============================
    上面既然可以使用ModelState.Clear();清空所有的模型状态,那是否也可以移除模型状态中的某些属性呢?

    以用户登录表单为例(用户名+密码),先看下模型定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    namespace FineUICore.Examples.WebForms.Pages.DataModel.Models
    {
        public class User
        {
            [Required]
            [Display(Name = "用户名")]
            [StringLength(20)]
            public string UserName { get; set; }
     
            [Required(ErrorMessage = "用户密码不能为空!", AllowEmptyStrings = true)]
            [Display(Name = "密码")]
            [MaxLength(9, ErrorMessage = "密码最大为 9 个字符!")]
            [MinLength(3, ErrorMessage = "密码最小为 3 个字符!")]
            [DataType(DataType.Password)]
            [RegularExpression("^(?:[0-9]+[a-zA-Z]|[a-zA-Z]+[0-9])[a-zA-Z0-9]*$", ErrorMessage = "密码至少包含一个字母和数字!")]
            public string Password { get; set; }
     
        }
    }

      

     

    页面模型中,定义一个名为 CurrentUser 的绑定属性:

    1
    2
    [BindProperty]
    public User CurrentUser { get; set; }

      

    在页面视图中,将用户名和密码通过两个文本输入框渲染到页面中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <f:Window Width="350" WindowPosition="GoldenSection" EnableClose="false" IsModal="false" Title="登录表单" ID="Window1">
        <Items>
            <f:SimpleForm ShowHeader="false" BodyPadding="10" ShowBorder="false" ID="SimpleForm1">
                <Items>
                    <f:TextBox For="CurrentUser.UserName">f:TextBox>
                    @* <f:TextBox For="CurrentUser.Password">f:TextBox> *@
                Items>
            f:SimpleForm>
        Items>
        <Toolbars>
            <f:Toolbar Position="Bottom" ToolbarAlign="Right" ID="Toolbar1">
                <Items>
                    <f:Button OnClick="btnLogin_Click" OnClickFields="SimpleForm1" ValidateTarget="Top" ValidateForms="SimpleForm1" Type="Submit" Text="登录" ID="btnLogin">f:Button>
                    <f:Button Type="Reset" Text="重置" ID="btnReset">f:Button>
                Items>
            f:Toolbar>
        Toolbars>
    f:Window>

      

    注意,上述代码中我们注释掉了 CurrentUser.Password,以便在后台验证模型状态验证失败的情况。

    此时提交表单,FineUICore会自动弹出模型验证失败的消息,如下图所示。

     

    这个逻辑上是没有问题的,那个弹出框提示是FineUICore系统处理的(无需用户编码),看下事件处理函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public IActionResult OnPostBtnLogin_Click()
    {
        if (ModelState.IsValid)
        {
            if (CurrentUser.UserName == "admin" && CurrentUser.Password == "admin888")
            {
                ShowNotify("成功登录!", MessageBoxIcon.Success);
            }
            else
            {
                ShowNotify(String.Format("用户名({0})或密码({1})错误!",
                    CurrentUser.UserName,
                    CurrentUser.Password), MessageBoxIcon.Error);
            }
        }
     
        return UIHelper.Result();
    }

      

    为了从模型验证状态中移除某些属性,我们可以直接这么写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public IActionResult OnPostBtnLogin_Click()
    {
        ModelState.Remove("CurrentUser.Password");
     
        if (ModelState.IsValid)
        {
        ...
        }
     
    }

      

    参考文章:https://stackoverflow.com/questions/16266988/exclude-fields-from-model-validation

    上述文章指出,调用 ModelState.Remove() 方法虽然不够优雅,但是可以快速解决问题。更加优雅的做法是自定义一个单独的视图模型,示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class PersonViewModel
    {
        [Required]
        public String FirstName { get; set; }
     
        [Required]
        public String LastName { get; set; }
    }
     
    public class PersonWithEmailViewModel : PersonViewModel
    {
        [Required]
        public String Email { get; set; }
    }

      

  • 相关阅读:
    Science子刊 | 将CAR-T细胞疗法与造血干细胞移植相结合 或许 能治疗所有血液癌症...
    Redis小记(一)
    艾美捷Mabtech抗人IFN-αmAb(MT1)未偶联方案
    一文搞懂如何学习Android内部命令行工具集合
    代码整洁之道-读书笔记之函数
    Windows 10怎么清理磁盘空间?
    React 共享组件状态及其实践
    Python的GIL存在的情况下,是否还有必要添加线程锁。
    Python数据分析与机器学习31-SVM案例:人脸识别
    深入理解Linux中信号处理过程
  • 原文地址:https://www.cnblogs.com/sanshi/p/18194672