这是一篇4年前的文章:【经验分享】在ASP.NET Core中,如果禁用某个请求的模型验证?
事隔多年,又有网友问到这个问题。我就来重新整理一下,顺便扩展一下之前的解决办法。
=====
这是一个来自网友【David】的提问。在 AppBoxCore 项目的新增用户页面,新增一个上传按钮:
1 2 3 4 5 |
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 ; } } |