原文:Model Validation

作者:Rachel Appel

翻译:娄宇(Lyrics)

校对:孟帅洋(书缘)

在这篇文章中:

章节:

介绍模型验证

在一个应用程序将数据存储到数据库之前,这个应用程序必须验证数据。数据必须检查潜在的安全隐患,验证类型和大小是正确并且符合你所制定的规则。尽管验证的实现可能会是冗余和繁琐的,却是有必要的。在 MVC 中,验证发生在客户端和服务器端。

幸运地是, .Net 有一些拥有抽象验证的验证 Attribute 。这些 Attribute 包含验证代码,从而减少你必须写的代码量。

验证 Attribute

验证 Attribute 是一种配置模型验证的方法,类似在数据库表中验证字段的概念。它包含了指定数据类型或者必填字段等约束。其它类型的验证包括将强制的业务规则应用到数据验证,比如验一个信用卡号,一个手机号码,或者一个 Email 地址。 验证 Attribute 使这些要求更简单,更容易使用。

下面是一个存储了电影和电视节目信息的应用程序中被注解的 Movie 模型。大部分属性是必填的,几个字符串类型的属性有长度限制。此外,在 Price 属性上通过自定义验证 Attribute 实现了 0 到 $999.99 的数字范围限制。

public class Movie
{
public int Id { get; set; } [Required]
[StringLength(100)]
public string Title { get; set; } [Required]
[ClassicMovie(1960)]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; } [Required]
[StringLength(1000)]
public string Description { get; set; } [Required]
[Range(0, 999.99)]
public decimal Price { get; set; } [Required]
public Genre Genre { get; set; } public bool Preorder { get; set; }
}

简单地通过阅读模型了解了这个应用程序的数据规则,(这种编码方式)让维护代码变得更简单。以下是几个常用的内置验证 Attribute :

  • [CreditCard]: 验证属性是信号卡号格式。
  • [Compare]: 验证模型中的两个属性匹配。
  • [EmailAddress]: 验证属性是 Email 格式。
  • [Phone]: 验证属性是 电话号码 格式。
  • [Range]: 验证属性在指定的范围内。
  • [RegularExpression]: 验证数据匹配指定的正则表达式。
  • [Required]: 使属性成为必填。
  • [StringLength]: 验证字符串类型属性的最大长度。
  • [Url]: 验证属性是 URL 格式。

MVC 支持任何为了验证目的而从 ValidationAttribute 继承的 Attribute 。需要有用的验证 Attribute 可以在 System.ComponentModel.DataAnnotations 命名空间下找到。

可能在某些情况下,你需要使用比内置 Attribute 更多的验证功能。在那时,你可以通过创建继承自 ValidationAttribute 的自定义验证 Attribute 或者修改你的模型去实现 IValidatableObject 接口。

模型状态

模型状态表示在 HTML 表单提交值的一系列验证错误。

MVC 将持续验证字段直到错误数达到最大值(默认200)。你可以通过在 Startup.cs 文件下的 ConfigureServices 方法中插入以下代码来配置这个最大值:

services.AddMvc(options => options.MaxModelValidationErrors = 50);

处理模型状态异常

模型验证发生在每个控制器(Controller)的行为(Action)被调用之前,而检查 ModelState.IsValid 和做出适当的反应是行为(Action)方法的职责。在许多情况下,适当的反映是返回某种错误响应,理想情况下详细介绍了模型验证失败的原因。

一些应用程序将选择遵循一个标准的惯例来处理模型验证错误,在这种情况下,过滤器可能是一个适当的方式来实现这种策略。你需要分别用有效和无效的模型状态来测试 Action 的行为。

手动验证

当模型绑定和验证完成后,你也许想重复其中的部分操作。例如,用户可能输入了一个被期望为 integer 类型的字段的文本,或者你需要为模型中的一个属性计算一个值。

你需要手动去执行验证。像这样,调用 TryValidateModel 方法:

TryValidateModel(movie);

自定义验证

验证 Attribute 满足大多数的验证需求。然而你的业务存在一些特殊的验证规则,它们不仅仅是通用的数据验证,如确保字段必填或者符合一个值的范围之类的。对于这些情况,自定义验证 Attribute 是一个不错的解决方案。在 MVC 中创建你自己的自定义验证 Attribute 是非常容易的。只需要继承 ValidationAttribute 并且重写 IsValid 方法。 IsValid 方法接受两个参数,第一个是命名为 value 的 object 对象,第二个参数是一个命名为 validationContextValidationContext 对象。 Value 指的是你的自定义验证器验证的字段的值。

在下面的示例中,一个业务规则规定,用户可能不会将在1960年之后发布的电影的 Genre 设置为 Classic[ClassicMovie] Attribute 首先检查 Genre ,如果它是 Genre.Classic ,接下来检查电影发布日期是否晚于1960年。如果发布晚于1960年,验证失败。这个 Attribute 接受一个 integer 类型的参数作为验证数据的年份。你可以在这个 Attribute 的构造函数中对这个值进行赋值,如同这里显示的:

public class ClassicMovieAttribute : ValidationAttribute, IClientModelValidator
{
private int _year; public ClassicMovieAttribute(int Year)
{
_year = Year;
} protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Movie movie = (Movie)validationContext.ObjectInstance; if (movie.Genre == Genre.Classic && movie.ReleaseDate.Year > _year)
{
return new ValidationResult(GetErrorMessage());
} return ValidationResult.Success;
}

上面的 movie 变量代表一个包含了表单提交数据并等待验证的 Movie 的对象。在这个例子中,ClassicMovieAttribute 类的 IsValid 方法按照规定检查了日期和分类( Genre )。当验证成功, IsValid 方法返回一个 ValidationResult.Success 枚举码;当验证失败,返回一个带有错误消息的 ValidationResult 。当用户修改了 Genre 字段并且提交表单, ClassicMovieAttribute 中的 IsValid 方法将验证电影是否是经典( Classic )。如同其他内置的 Attribute 一样,应用 ClassicMovieAttribute 到比如 ReleaseDate 这个属性上来确保验证发生,如果之前例子中的演示代码一样。因为这个例子仅对 Movie 类型有效,一个更好的选择使用下面段落介绍的 IValidatableObject

另外,相同的代码可以放在模型里,通过去实现 IValidatableObject 接口中的 Validate 方法。当自定义验证 Attribute 能够很好的验证各个属性时,实现 IValidatableObject 接口可以用来实现类等级(Class-Level)的验证,如下。

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
{
yield return new ValidationResult(
"Classic movies must have a release year earlier than " + _classicYear,
new[] { "ReleaseDate" });
}
}

客户端验证

客户端验证为客户带了极大的便利。它可以节省时间而不用花费一个来回时间等待服务器的验证结果。在业务角度来看,一天中哪怕是几秒乘以数百次,都会增加很多工作时间、开支以及挫败感。直接和即时的验证,使用户能够更有效地工作,得到质量更好的投入和产出。

你必须适当的引用 JavaScript 脚本来进行客户端验证,如下。

<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.3.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"></script>

除了模型属性的类型元数据外,MVC还是用验证 Attribute 通过 JavaScript 验证数据并展示所有错误信息。当你使用 MVC 去渲染使用 Tag Helpers 或者 HTML helpers 的表单数据之时,它将在需要验证的表单元素中添加 HTML 5 data- attributes,如同下面看到的。 MVC 对所有内置验证 Attribute 和自定义验证 Attribute 生成 data- 特性。你可以通过相关的 Tag Helper 在客户端显示验证错误,如同这里展示的:

<div class="form-group">
<label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
</div>

上面的 Tag Helper 渲染的 HTML 如下。 注意输出的 HTML 中 data- 特性对应 ReleaseDate 属性的验证 Attribute。下面的 data-val-required 特性包含一个用于展示的错误消息,如果用户没有填写 ReleaseDate 字段,错误消息将随着 <span> 元素一起显示。

<form action="/movies/Create" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<div class="text-danger"></div>
<div class="form-group">
<label class="col-md-2 control-label" for="ReleaseDate">ReleaseDate</label>
<div class="col-md-10">
<input class="form-control" type="datetime"
data-val="true" data-val-required="The ReleaseDate field is required."
id="ReleaseDate" name="ReleaseDate" value="" />
<span class="text-danger field-validation-valid"
data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
</div>
</div>
</div>
</form>

客户端验证防止表单提交直到有效为止。无论提交表单还是显示错误消息,提交按钮都会执行 JavaScript 代码。

MVC 基于 .NET 属性的数据类型决定类型特性值,可以使用 [DataType] Attribute 来覆盖。基础的 [DataType] Attribute 并不是真正的服务端认证。浏览器选择它们自己的错误消息并按照它们希望的那样显示这些错误,然而 jQuery Validation Unobtrusive 包可以重写这些消息并且让他们显示方式保持一致。当用户应用 [DataType] 的子类比如 [EmailAddress] 的时候,这种情况最明显。

客户端模型验证器

你也许会为你的自定义 Attribute 创建客户端逻辑,unobtrusive validation 会在客户端将它作为验证的一部分自动执行。第一步

是向下面一样,通过实现 IClientModelValidator 接口来控制那些被添加的 data- 特性:

public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
} MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage()); var year = _year.ToString(CultureInfo.InvariantCulture);
MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
}

Attribute 实现这个接口后可以添加 HTML 特性到生成的字段。检查输出的 HTML 中的 ReleaseDate 元素,和上一个例子差不多,除了通过 IClientModelValidator 接口的 AddValidation 方法定义了一个 data-val-classicmovie 特性。

<input class="form-control" type="datetime"
data-val="true"
data-val-classicmovie="Classic movies must have a release year earlier than 1960"
data-val-classicmovie-year="1960"
data-val-required="The ReleaseDate field is required."
id="ReleaseDate" name="ReleaseDate" value="" />

Unobtrusive validation 使用 data- 特性中的数据来显示错误消息。然而 JQuery 在你添加 JQuery 的 validator 对象之前是不知道规则和消息的。在显示在下面的例子中将一个包含自定义客户端验证代码的命名为 classicmovie 的方法添加到 JQuery 的 validator 对象中。

$(function () {
jQuery.validator.addMethod('classicmovie',
function (value, element, params) {
// Get element value. Classic genre has value '0'.
var genre = $(params[0]).val(),
year = params[1],
date = new Date(value);
if (genre && genre.length > 0 && genre[0] === '0') {
// Since this is a classic movie, invalid if release date is after given year.
return date.getFullYear() <= year;
} return true;
}); jQuery.validator.unobtrusive.adapters.add('classicmovie',
[ 'element', 'year' ],
function (options) {
var element = $(options.form).find('select#Genre')[0];
options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
options.messages['classicmovie'] = options.message;
});
}(jQuery));

现在 JQuery 拥有执行自定义 JavaScript 验证以及当验证代码返回 false 时用来显示的错误消息的信息了。

远程验证

当你需要在客户端上使用服务器上的数据进行验证的时候,远程验证是一个很棒的功能。比如,你的应用程序也许需要验证一个 Email 或者用户名是否已经被使用,这样做必须查询大量的数据。为了验证一个或几个字段下载大量的数据,消耗了过多的资源。并且可能会暴露敏感信息。另一个办法是使用回传请求来验证字段。

你可以用两个步骤实现远程验证。首先,你需要用 [Remote] Attribute 注解你的模型。[Remote] Attribute 接受多个重载可以直接使用客户端 JavaScript 到适当的代码来调用。下面的例子指向 Users Controller 的 VerifyEmail Action 。

public class User
{
[Remote(action: "VerifyEmail", controller: "Users")]
public string Email { get; set; }
}

第二步是将验证代码放到 [Remote] Attribute 中定义的相应 Action 方法中。Action 方法返回一个 JsonResult ,如果需要,客户端可以用来继续或者暂停并显示错误。

[AcceptVerbs("Get", "Post")]
public IActionResult VerifyEmail(string email)
{
if (!_userRepository.VerifyEmail(email))
{
return Json(data: $"Email {email} is already in use.");
} return Json(data: true);
}

现在当用户输入一个 Email ,View 中的 JavaScript 进行远程调用来检查 Email 是否被占用,如果被占用就显示错误消息。否则,用户可以和往常一样提交表单。

返回目录

ASP.NET Core 中文文档 第四章 MVC(2.2)模型验证的更多相关文章

  1. ASP.NET Core 中文文档 第四章 MVC(3.6.2 )自定义标签辅助类(Tag Helpers)

    原文:Authoring Tag Helpers 作者:Rick Anderson 翻译:张海龙(jiechen) 校对:许登洋(Seay) 示例代码查看与下载 从 Tag Helper 讲起 本篇教 ...

  2. ASP.NET Core 中文文档 第四章 MVC(4.2)控制器操作的路由

    原文:Routing to Controller Actions 作者:Ryan Nowak.Rick Anderson 翻译:娄宇(Lyrics) 校对:何镇汐.姚阿勇(Dr.Yao) ASP.NE ...

  3. ASP.NET Core 中文文档 第四章 MVC(3.6.1 )Tag Helpers 介绍

    原文:Introduction to Tag Helpers 作者:Rick Anderson 翻译:刘浩杨 校对:高嵩(Jack) 什么是 Tag Helpers? Tag Helpers 提供了什 ...

  4. ASP.NET Core 中文文档 第四章 MVC(3.8)视图中的依赖注入

    原文:Dependency injection into views 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:孟帅洋(书缘) ASP.NET Core 支持在视图中使用 依赖 ...

  5. ASP.NET Core 中文文档 第四章 MVC(4.6)Areas(区域)

    原文:Areas 作者:Dhananjay Kumar 和 Rick Anderson 翻译:耿晓亮(Blue) 校对:许登洋(Seay) Areas 是 ASP.NET MVC 用来将相关功能组织成 ...

  6. ASP.NET Core 中文文档 第四章 MVC(4.5)测试控制器逻辑

    原文: Testing Controller Logic 作者: Steve Smith 翻译: 姚阿勇(Dr.Yao) 校对: 高嵩(Jack) ASP.NET MVC 应用程序的控制器应当小巧并专 ...

  7. ASP.NET Core 中文文档 第四章 MVC(4.4)依赖注入和控制器

    原文: Dependency Injection and Controllers 作者: Steve Smith 翻译: 刘浩杨 校对: 孟帅洋(书缘) ASP.NET Core MVC 控制器应通过 ...

  8. ASP.NET Core 中文文档 第四章 MVC(4.1)Controllers, Actions 和 Action Results

    原文:Controllers, Actions, and Action Results 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:许登洋(Seay) Action 和 acti ...

  9. ASP.NET Core 中文文档 第四章 MVC(3.9)视图组件

    作者: Rick Anderson 翻译: 娄宇(Lyrics) 校对: 高嵩 章节: 介绍视图组件 创建视图组件 调用视图组件 演练:创建一个简单的视图组件 附加的资源 查看或下载示例代码 介绍视图 ...

  10. ASP.NET Core 中文文档 第四章 MVC(3.7 )局部视图(partial)

    原文:Partial Views 作者:Steve Smith 翻译:张海龙(jiechen).刘怡(AlexLEWIS) 校对:许登洋(Seay).何镇汐.魏美娟(初见) ASP.NET Core ...

随机推荐

  1. 让姑姑不再划拳 码农也要有原则 : SOLID via C#

    “姑娘,别这样.我们是有原则的.” “一个有原则的程序猿是不会写出 “摧毁地球” 这样的程序的,他们会写一个函数叫 “摧毁行星”而把地球当一个参数传进去.” “对,是时候和那些只会滚键盘的麻瓜不同了, ...

  2. Java基础Map接口+Collections工具类

    1.Map中我们主要讲两个接口 HashMap  与   LinkedHashMap (1)其中LinkedHashMap是有序的  怎么存怎么取出来 我们讲一下Map的增删改查功能: /* * Ma ...

  3. 无法向会话状态服务器发出会话状态请求。请确保 ASP.NET State Service (ASP.NET 状态服务)已启动,并且客户端端口与服务器端口相同。如果服务器位于远程计算机上,请检查。。。

    异常处理汇总-服 务 器 http://www.cnblogs.com/dunitian/p/4522983.html 无法向会话状态服务器发出会话状态请求.请确保 ASP.NET State Ser ...

  4. C# DateTime日期格式化

    在C#中DateTime是一个包含日期.时间的类型,此类型通过ToString()转换为字符串时,可根据传入给Tostring()的参数转换为多种字符串格式. 目录 1. 分类 2. 制式类型 3. ...

  5. python黑魔法 -- 内置方法使用

    很多pythonic的代码都会用到内置方法,根据自己的经验,罗列一下自己知道的内置方法. __getitem__ __setitem__ __delitem__ 这三个方法是字典类的内置方法,分别对应 ...

  6. 神经网络、logistic回归等分类算法简单实现

    最近在github上看到一个很有趣的项目,通过文本训练可以让计算机写出特定风格的文章,有人就专门写了一个小项目生成汪峰风格的歌词.看完后有一些自己的小想法,也想做一个玩儿一玩儿.用到的原理是深度学习里 ...

  7. ASP.NET Core应用中如何记录和查看日志

    日志记录不仅对于我们开发的应用,还是对于ASP.NET Core框架功能都是一项非常重要的功能特性.我们知道ASP.NET Core使用的是一个极具扩展性的日志系统,该系统由Logger.Logger ...

  8. Android 几种消息推送方案总结

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6241354.html 首先看一张国内Top500 Android应用中它们用到的第三方推送以及所占数量: 现 ...

  9. Function.prototype.toString 的使用技巧

    Function.prototype.toString这个原型方法可以帮助你获得函数的源代码, 比如: function hello ( msg ){ console.log("hello& ...

  10. Android的Kotlin秘方(II):RecyclerView 和 DiffUtil

    作者:Antonio Leiva 时间:Sep 12, 2016 原文链接:http://antonioleiva.com/recyclerview-diffutil-kotlin/ 如你所知,在[支 ...