ASP.NET Core 模型验证系列二

1 Model级别错误消息

Model层错误消息被用到整个实体,而不是单个属性,我们只需要给AddModelError() 方法第一个参数中提供一个空值,如下代码:

ModelState.AddModelError("", "Some Model-Level error");

采用一个例子,假如我们不想让名字为Osama Bin的这个人申请Job,为了实现这个,我们添加一个新的else if 语句块检查名字是否是‘Osama Bin Laden’,然后提供一个实体类级别的错误:

[HttpPost]
public IActionResult Index(JobApplication jobApplication)
{
    if (string.IsNullOrEmpty(jobApplication.Name))
        ModelState.AddModelError(nameof(jobApplication.Name), "请输入用户名");
    else if (jobApplication.Name == "Osama Bin Laden")
        ModelState.AddModelError("", "你不能申请工作");
    // removed for clarity
}

我们把 asp-validation-summary属性修改为ModelOnly‍

@model JobApplication
@{
    ViewData["Title"] = "Job Application";
}
<style>
    .input-validation-error {
        border-color: red;
    }
</style>
<h2>Job Application</h2>
@*<div asp-validation-summary="All" class="text-danger"></div>*@
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<form class="m-1 p-1" method="post">
    <div class="mb-3 row">
        <div class="col-sm-1">
            <label asp-for="Name" class="control-label"></label>
        </div>
        <div class="col-sm-11">
            <input asp-for="Name" class="form-control" />
            <span asp-validation-for="Name" class="text-danger"></span>
        </div>
    </div>
    <div class="mb-3 row">
        <div class="col-sm-1">
            <label asp-for="DOB" class="control-label"></label>
        </div>
        <div class="col-sm-11">
            <input asp-for="DOB" type="text" asp-format="{0:d}" class="form-control" />
            <span asp-validation-for="DOB" class="text-danger"></span>
        </div>
    </div>
    <div class="mb-3 row">
        <div class="col-sm-1">
            <label asp-for="Sex" class="control-label"></label>
        </div>
        <div class="col-sm-1">
            <input asp-for="Sex" type="radio" value="M" class="form-check" />男
        </div>
        <div class="col-sm-1">
            <input asp-for="Sex" type="radio" value="F" class="form-check" />女
        </div>
        <div class="col-sm-9">
            <span asp-validation-for="Sex" class="text-danger"></span>
        </div>
    </div>
    <div class="mb-3 row">
        <div class="col-sm-1">
            <label asp-for="Experience" class="control-label"></label>
        </div>
        <div class="col-sm-11">
            <select asp-for="Experience" class="form-control">
                <option value="选择">选择</option>
                <option value="0">新手</option>
                <option value="1">0-1 年</option>
                <option value="2">1-2 年</option>
                <option value="3">2-3 年</option>
                <option value="4">3-4 年</option>
                <option value="5">4-5 年</option>
            </select>
            <span asp-validation-for="Experience" class="text-danger"></span>
        </div>
    </div>
    <div class="mb-3 row">
        <div class="col-sm-1">
        </div>
        <div class="col-sm-1">
            <input asp-for="TermsAccepted" class="form-label" />
        </div>
        <div class="col-sm-10">
            <label asp-for="TermsAccepted" class="form-check-label">
                我接受条款 & 条件
            </label>
            <span asp-validation-for="TermsAccepted" class="text-danger"></span>
        </div>
    </div>
    <div class="mb-3 row">
        <div class="col-sm-11 offset-sm-1">
            <button type="submit" class="btn btn-primary">提交</button>
        </div>
    </div>
</form>

我们在名字控件中输入Osama Bin Laden值,我们仅仅看到了错误消息-你不能申请工作

看下面图片:

edd08336c32b1f06b6b51579b9732ab1.png

2 模型验证中使用Data Annotations

ASP.NET Core 内置了大量的验证特性,我们可以在实体类上应用这些规则来指定验证规则,这些特性位于该命名空间(System.ComponentModel.DataAnnotations),验证处理过程是即简单又快,也可以少些一些验证的代码

微软在该命名空间下提供了大量的验证特性,我们也可以在应用程序中使用他们来做验证,可以在模型类的属性指定特性来验证属性,特性中定义了一系列的验证规则

下面列出了常用的特性:

名称

描述

[Required(ErrorMessage = 

“Some Message”)]

确保值不为空,默认值为null的类型指定一个值 例如:int?, float?, string

[StringLength(max,min)]

给字符串长度指定一个范围(包含最小值和最大值).例如[StringLeght(2,5)] 允许字符串的长度是2到5

[Compare(“OtherProperty”)]

确保应用这个特性的属性和指定的属性(即OtherProperty) 有相同的值

[Range(min,max)]

确保数字的值位于最小值和最大值之间(包含最大和最小值)

例如:[Range(2,5)]  包含的值 2,3,4,5

[RegularExpression(“pattern”)]

给指定的属性设置正则表单时,例如:邮件地址

现在我们进入JobApplication类并且引用命名空间System.ComponentModel.DataAnnotations,将特性添加到属性上:

using System.ComponentModel.DataAnnotations;
namespace ModelBindingValidation.Models
{
    public class JobApplication
    {
        [Required]
        [Display(Name = "Job applicant name")]
        public string Name { get; set; }
        public DateTime DOB { get; set; }
        [Required(ErrorMessage = "Please select your sex")]
        public string Sex { get; set; }
        [Range(0, 5)]
        public string Experience { get; set; }
        [Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the Terms")]
        public bool TermsAccepted { get; set; }
    }
}

名字上我们应用了2个特性,分别是:Display Required

[Required]
[DisplayName("姓名")]
public string Name { get; set; }

[DisplayName("姓名")]特性 指定属性在视图上显示的名字, [Required]特性指定属性必须有一个值

我们注意到[Required]特性没有指定ErrorMessage的值,该属性在视图上显示的名称是姓名,系统会自动采用这个名称的值作为错误消息

不需要指定任何验证特性针对时间类型的DOB字段,这是因为DateTime字段有一个01-01-0001 00:00:00 的默认值,因此用户在没有填充任何值的情况下,会采用默认值,因此使用[Required] 特性是不起作用

如果想要在属性上指定一个[Required]特性,我们必须该类型设置为nullable类型:

[Required(ErrorMessage = "你输入你的出生日期")]
public DateTime? DOB { get; set; }

在上面的代码中,我们应用了[Required(ErrorMessage = "")] 特性,确保DOB字段的值不能为空,并且有正确的时间格式,以相同的方式我们把[Required]特性应用到Sex字段

在Experience属性上指指定了一个Range特性,这是因为我们想要经验0到5

[Range(0, 5)]
public int Experience { get; set; }

最后我们在TermsAccepted字段应用一个[Range]特性:

[Range(typeof(bool), "true", "true", ErrorMessage = "你必须接受条款")]
[DisplayName("条款")]
public bool TermsAccepted { get; set; }

我们不能在用户选择条款上应用[Required]特性,这是因为一个bool类型的字段,如果不选择的话,默认值是false,因此Required 对该属性不起任何作用

我们把[Range]特性应用到bool类型的属性上面,设置最大和最小值为true,强迫用户选择checkbox

最后,我们把action方法中验证的代码移除掉,修改JobController的Index方法:

using AspNetCore.ModelValidation.Models;
using Microsoft.AspNetCore.Mvc;
namespace AspNetCore.ModelValidation.Controllers
{
    public class JobController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
        [HttpPost]
        public IActionResult Index(JobApplication jobApplication)
        {
            if (ModelState.IsValid)
                return View("Accepted", jobApplication);
            else
                return View();
        }
    }
}

现在我们仅仅验证ModelState对象的IsValid属性,重新运行应用程序并且看到表单和之前一样工作使用简单的代码

5109574137daeb9d2a78af746c42566b.png

下面代码包含action方法的验证:

[HttpPost]
public IActionResult Index(JobApplication jobApplication)
{
    if (jobApplication.Name == "Osama Bin Laden")
        ModelState.AddModelError(nameof(jobApplication.Name), "You cannot apply for the Job");
    if (jobApplication.DOB > DateTime.Now)
        ModelState.AddModelError(nameof(jobApplication.DOB), "Date of Birth cannot be in the future");
    else if (jobApplication.DOB < new DateTime(1980, 1, 1))
        ModelState.AddModelError(nameof(jobApplication.DOB), "Date of Birth should not be before 1980");
    if (ModelState.IsValid)
        return View("Accepted", jobApplication);
    else
        return View();
}

2.1 模型验证Email地址

为了确保邮件地址是正确的格式,我们使用[RegularExpression]特性进行验证,我们可以给该特性提供一个正则表达式

^[a-zA-Z0-9_\\.-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$

因此我们在JobApplication.cs 类中,添加一个新的email字段,代码如下:

using System.ComponentModel.DataAnnotations;
namespace ModelBindingValidation.Models
{
    public class JobApplication
    {
        [Required]
        [Display(Name = "Job applicant name")]
        public string Name { get; set; }
        public DateTime DOB { get; set; }
        [Required(ErrorMessage = "Please select your sex")]
        public string Sex { get; set; }
        [Range(0, 5)]
        public string Experience { get; set; }
        [Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the Terms")]
        public bool TermsAccepted { get; set; }
        [RegularExpression("^[a-zA-Z0-9_\\.-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "E-mail is not valid")]
        public string Email { get; set; }
    }
}

进入View 添加如下代码

@model JobApplication
@{
    ViewData["Title"] = "Job Application";
}
<style>
    .input-validation-error {
        border-color: red;
    }
</style>
<h2>Job Application</h2>
@*<div asp-validation-summary="All" class="text-danger"></div>*@
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<form class="m-1 p-1" method="post">
    <div class="mb-3 row">
        <div class="col-sm-1">
            <label asp-for="Name" class="control-label"></label>
        </div>
        <div class="col-sm-11">
            <input asp-for="Name" class="form-control" />
            <span asp-validation-for="Name" class="text-danger"></span>
        </div>
    </div>
    <div class="mb-3 row">
        <div class="col-sm-1">
            <label asp-for="DOB" class="control-label"></label>
        </div>
        <div class="col-sm-11">
            <input asp-for="DOB" type="text" asp-format="{0:d}" class="form-control" />
            <span asp-validation-for="DOB" class="text-danger"></span>
        </div>
    </div>
    <div class="mb-3 row">
        <div class="col-sm-1">
            <label asp-for="Email" class="control-label"></label>
        </div>
        <div class="col-sm-11">
            <input asp-for="Email" type="text" asp-format="{0:d}" class="form-control" />
            <span asp-validation-for="Email" class="text-danger"></span>
        </div>
    </div>
    <div class="mb-3 row">
        <div class="col-sm-1">
            <label asp-for="Sex" class="control-label"></label>
        </div>
        <div class="col-sm-1">
            <input asp-for="Sex" type="radio" value="M" class="form-check" />男
        </div>
        <div class="col-sm-1">
            <input asp-for="Sex" type="radio" value="F" class="form-check" />女
        </div>
        <div class="col-sm-9">
            <span asp-validation-for="Sex" class="text-danger"></span>
        </div>
    </div>
    <div class="mb-3 row">
        <div class="col-sm-1">
            <label asp-for="Experience" class="control-label"></label>
        </div>
        <div class="col-sm-11">
            <select asp-for="Experience" class="form-control">
                <option value="选择">选择</option>
                <option value="0">新手</option>
                <option value="1">0-1 年</option>
                <option value="2">1-2 年</option>
                <option value="3">2-3 年</option>
                <option value="4">3-4 年</option>
                <option value="5">4-5 年</option>
            </select>
            <span asp-validation-for="Experience" class="text-danger"></span>
        </div>
    </div>
    <div class="mb-3 row">
        <div class="col-sm-1">
        </div>
        <div class="col-sm-1">
            <input asp-for="TermsAccepted" class="form-label" />
        </div>
        <div class="col-sm-10">
            <label asp-for="TermsAccepted" class="form-check-label">
                我接受条款 & 条件
            </label>
            <span asp-validation-for="TermsAccepted" class="text-danger"></span>
        </div>
    </div>
    <div class="mb-3 row">
        <div class="col-sm-11 offset-sm-1">
            <button type="submit" class="btn btn-primary">提交</button>
        </div>
    </div>
</form>

当Email格式不正确时,将显示下面错误消息:

61f4fb61f9d927551f7499e3c2a0c9f4.png

注意:我们也能使用[EmailAddress] 验证特性做email验证

[EmailAddress]
public string Email { get; set; }

3 自定义模型验证

如何在.NET Core中创建一个自定义的验证特性?客户自定义模型可以允许我们业务代码来处理验证

我们需要继承这个类:

1 Attribute 类

2 IModelValidator 接口

这个Attribute类是自定义特性的基类,IModelValidator是一个接口定义了Validate() 方法,我们实现这个接口并在接口中写自定义验证代码

Validate() 方法的架子如下:

public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context) 
{
    ...
}

在参数中,我们能通过ModelValidationContext类实例接收到关于属性的信息,ModelValidationContext类属性如下:

姓名

描述

Model

返回验证的属性的值

Container

返回属性所拥有的对象

ActionContext

提供上下文数据

在这个例子中,我们给DOB属性创建一个客户自定义属性,因此首先在根目录下创建一个Infrastructure 文件夹,并且在文件夹下创建一个CustomDate.cs类,下一步,添加下面代码:

using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;


namespace AspNetCore.ModelValidation.Infrastructure
{
    public class CustomDate : Attribute, IModelValidator
    {
        public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
        {
            if (Convert.ToDateTime(context.Model) > DateTime.Now)
                return new List<ModelValidationResult> {
                    new ModelValidationResult("", "日期不能大于当前日期")
                };
            else if (Convert.ToDateTime(context.Model) < new DateTime(1980, 1, 1))
                return new List<ModelValidationResult> {
                    new ModelValidationResult("", "日期必须在1980年以前")
                };
            else
                return Enumerable.Empty<ModelValidationResult>();
        }
    }
}

在Validate()方法内针对DOB字段指定客户自定义验证,在这里满足两个条件:

1 时间大于当前时间

2 时间小于1980年

如果这两个条件失败,返回ModelValidationResult的List对象,ModelValidationResult构造函数的第一个参数是和错误关联的属性名字,当验证单个属性时,它被指定为空,第二个参数是错误消息,如果这两个条件都通过,我们将返回一个空的enemurable 对象-return Enumerable.Empty()

现在在DOB字段上添加这个特性:

using AspNetCore.ModelValidation.Infrastructure;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;


namespace AspNetCore.ModelValidation.Models
{
    public class JobApplication
    {
        [Required(ErrorMessage ="姓名不能为空")]
        [DisplayName("姓名")]
        public string Name { get; set; }


        [DisplayName("出生日期")]
        [Required(ErrorMessage = "请输入你的出生日期")]
        [CustomDate]
        public DateTime? DOB { get; set; }


        [Required(ErrorMessage = "请选择性别")]
        [DisplayName("性别")]
        public string Sex { get; set; }


        [Range(0, 5,ErrorMessage ="工作年限必须在0-5年")]
        [DisplayName("工作经验")]
        public string? Experience { get; set; }


        [Range(typeof(bool), "true", "true", ErrorMessage = "你必须接受条款")]
        [DisplayName("条款")]
        public bool TermsAccepted { get; set; }


        [RegularExpression("^[a-zA-Z0-9_\\.-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "电子邮件不正确")]
        [DisplayName("电子邮件")]
        public string Email { get; set; }
    }
}

我们可以把action 方法里面验证DOB字段的代码移除掉:

[HttpPost]
public IActionResult Index(JobApplication jobApplication)
{
    if (ModelState.IsValid)
        return View("Accepted", jobApplication);
    else
        return View();
}

现在,我们测试一下,下面图片显示错误消息:

e83d873716f5dd3e4e80557f3f2635d7.png

我们针对另外一个属性创建客户自定义验证,阻止一些用户使用特定名字,因此,在Infrastructure文件夹下创建一个新的类叫NameValidate.cs并且添加下面代码:

namespace AspNetCore.ModelValidation.Infrastructure
{
    public class NameValidate : Attribute, IModelValidator
    {
        public string[] NotAllowed { get; set; }
        public string ErrorMessage { get; set; }
        public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
        {
            if (NotAllowed.Contains(context.Model as string))
                return new List<ModelValidationResult> {
                    new ModelValidationResult("", ErrorMessage)
                };
            else
                return Enumerable.Empty<ModelValidationResult>();
        }
    }
}

当在属性上面使用[NameValidate]特性时,我们需要提供一个值,这里:

ErrorMessage – 错误信息

NotAllowed –字符串的数组,表示你不想让用户输入的名字

现在只需要在name字段上添加特性:

[Required]
[Display(Name = "Job applicant name")]
[NameValidate(NotAllowed = new string[] { "Osama Bin Laden", "Saddam Hussain", "Mohammed Gaddafi" }, ErrorMessage = "You cannot apply for the Job")]
public string Name { get; set; }

使用这个特性,像 "Osama Bin Laden", "Saddam Hussain", "Mohammed Gaddafi" 将不能申请工作

当名字是Saddam Hussain,将给与下面错误信息:

bce2f41291b8708b485dd7ff685931c5.png

4 客户端验证

直到现在我们使用的验证都是服务器端验证,这意味着表单需要提交到服务器端才能完成验证工作,然后服务器端发送错误消息到客户端,网络顺畅的情况下会很快,如果网络比较慢的情况下验证会执行很慢

如果想在浏览器中立即获得验证而没有延迟,我们必须使用客户端验证,这些验证在浏览器端完成,而不是去服务器绕一圈再返回,我们可以通过在客户端调用JQuery库来完成这个工作

为了指定客户端验证,我们需要在应用程序中添加3个文件:

1 jQuery 

2 jQuery Validation

3 jQuery Validation Unobtrusive

在JobController的Index视图中引用这3个文件:

@model JobApplication
@{
    Layout = "_Layout";
    ViewData["Title"] = "Job Application";
}
@section scripts {
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
}
<style>
    .input-validation-error {
        border-color: red;
    }
</style>
<h2>Job Application</h2>

现在我们重新运行应用程序,客户端验证会立即触发,而没有去服务器验证

客户端验证不能工作针对Range特性处理bool值,因此你需要注释掉Range特性,因此需要在客户端单独处理

代码如下:

//[Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the Terms")]
public bool TermsAccepted { get; set; }

客户端验证也不支持像用户自定义模型证 [CustomDate]和[NameValidate]这对这些验证你需要在客户端创建验证

5 ASP.NET Core 远程验证

远程验证是通过异步方式验证,尽管看上去像客户端验证,但实际上通过Ajax在服务器端完成,远程验证在服务器端进行,当控件失去焦点时出发验证验证,而不是点击提交按钮

为了创建远程验证,我们需要添加一个action方法,返回JsonResult对象,有一个DOB的参数

我们把DOB属性上验证Attribute移除掉,添加一个新的action方法调用ValidateDate在JobController,代码如下:

public JsonResult ValidateDate(DateTime DOB)
{
     if (DOB > DateTime.Now)
          return Json("日期必须大于当前时间");
     else if (DOB < new DateTime(1980, 1, 1))
          return Json("日期不能再1980年以前");
     else
          return Json(true);
}

如果时间大于当前时间或者小于1980年,在这种情况下会以JSON格式返回错误的字符串,如果时间格式是正确的,JSON返回true

现在注释掉[CustomDate]特性在JobApplication.cs类,并添加[Remote]特性代码如下:

using AspNetCore.ModelValidation.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;


namespace AspNetCore.ModelValidation.Models
{
    public class JobApplication
    {
        [Required(ErrorMessage ="姓名不能为空")]
        [DisplayName("姓名")]
        [NameValidate(NotAllowed = new string[] { "Osama Bin Laden", "Saddam Hussain", "Mohammed Gaddafi" }, 
            ErrorMessage = "你不能申请这份工作")]
        public string Name { get; set; }
        
        [DisplayName("出生日期")]
        [Required(ErrorMessage = "请输入你的出生日期")]
        [Remote("ValidateDate", "Job")]
        public DateTime? DOB { get; set; }


        [Required(ErrorMessage = "请选择性别")]
        [DisplayName("性别")]
        public string Sex { get; set; }


        [Range(0, 5,ErrorMessage ="工作年限必须在0-5年")]
        [DisplayName("工作经验")]
        public string? Experience { get; set; }


        [Range(typeof(bool), "true", "true", ErrorMessage = "你必须接受条款")]
        [DisplayName("条款")]
        public bool TermsAccepted { get; set; }


        [RegularExpression("^[a-zA-Z0-9_\\.-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "电子邮件不正确")]
        [DisplayName("电子邮件")]
        [Required(ErrorMessage="电子邮件不能为空")]
        public string Email { get; set; }
    }
}

Remote特性有两个参数:

1 方法名字 

2 控制器名

现在我们输入DOB字段来检查验证,注意当鼠标移走时,远程验证将被调用

167258ad57d090a198a60f9d37b4feba.png

源代码地址

https://github.com/bingbing-gui/Asp.Net-Core-Skill/tree/master/Fundamentals/AspNetCore.ModelValidation/AspNetCore.ModelValidation

参考文献

https://www.yogihosting.com/aspnet-core-model-validation/#custom-model-validation

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值