前言:
最近在学习ASP.NET的MVC,准备以后用MVC来写网页。在园子里找教程找到了这个:MVC4学习笔记(一)- 认识MVC,这篇教程又推荐了这个:Asp.Net MVC4入门指南(1): 入门介绍。而后面这篇教程是由葡萄城控件技术团队博客翻译的ASP.NET官方网站的一篇教程:Getting Started with ASP.NET MVC 4 (9 Tutorials)。以上教程对于从没接触过ASP.NET的MVC人来说是非常不错的,如果你已经有了一些MVC的编程经验,也许会觉得ASP.NET官网的这篇教程比较基础,那么请寻找适合你程度的教程。
学习葡萄城控件技术团队的教程使我踏入了MVC这门技术,跟我之前用Web项目和WebForm项目编写网页的风格大相径庭。不过当学到第8篇《给数据模型添加校验器》时却出了点儿小状况——链接错了还是什么原因,返回了一个404页面。还差最后两节就把“入门武功”学完了,于是就翻原文http://www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/adding-validation-to-the-model,一点一点看下去,发现也不是天书,有了前7篇的基础,这一篇大部分都能看懂。而且我本人也是英语很烂,想借此为重新学习英语开个头,于是乎就想自己来翻译一下。现在将自己的译文写入博客,以勉励自己继续学习英语,但是本篇译文不保证翻译完全准确,大概意思应该是差不多。请看此文的朋友尽量参考原文。
最后感谢所有帮助我完成此文的人(不一一道出,望谅解)!
译文:
给数据模型添加校验器
在这节里,您将给Movie数据模型添加一个验证逻辑。并且您将确保使用者任何时刻使用应用程序来创建和编辑一条电影记录都要符合验证逻辑。
对工作遵守DRY
ASP.NET MVC的核心设计原则之一是DRY(不要重复你自己)。ASP.NET MVC支持你在一个程序里仅仅指定一次某项功能或行为,然后让它被到处反射使用。这样会减少你需要编写的代码量,并使你写的代码错误更少,更易于维护(维护更简单)。
由ASP.NET MVC和Entity Framework代码优先提供的验证支持是DRY原则在实际运用中一个很棒的例子。你能显式地具体验证规则在一个地方(在数据模型类中)并且该验证规则能在程序中每一处执行。
让我们来看看你怎样能在程序中利用验证支持。
往Movie数据模型中增加验证规则
你将从想Movie类中添加一些验证逻辑开始。
打开Movie.cs文件。在文件的顶部添加一个using声明,这个声明将引用System.ComponentModel.DataAnnotations命名空间:
using System.ComponentModel.DataAnnotations;
注意命名空间不要包含System.Web。DataAnnotations提供一个内置的验证特性集,你能够显式地应用该特性到任何的类或属性上。
现在修改Movie类来利用内置的必填、字符长度和范围验证特性。使用下面代码作为一个在何处运用特性的例子。
public class Movie {
public int ID { get; set; }
[Required]
public string Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Required]
public string Genre { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[StringLength(5)]
public string Rating { get; set; }
}
运行程序,你又会看到下面的运行时错误:
The model backing the 'MovieDBContext' context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269).
我们将使用迁移功能来更改数据库框架。生成解决方案,然后打开包管理器控制台窗口,并输入下述命令:
add-migration AddDataAnnotationsMig
update-database
当这条命令完成时,Visual Studio编辑器打开类文件,这个类文件定义了新的DbMigration的派生类,这个派生类带有一个规定为AddDataAnnotationsMig的名称,并且在Up方法中你能看到更新框架约束的代码。Title和Genre字段不在为空(那意味着,你必须输入一个值),并且Rating字段有一个最大为5的长度。
验证特性指定你想要强加在数据模型被应用的属性上。必填特性表明一个属性必须有一个值;在这个例子中,为了一个Movie的Title、ReleaseDate、Genre和Price属性有效,它们就必须有值。范围特性约束一个值在一个指定的范围内。字符长度特性让你设置一个字符属性的最大字数,并且不限该字符属性的最小字数。固有类型(例如decimal、int、float、DateTime)有默认值要求,这样它们不需要必填特性。
在应用程序保存改变到数据库里之前,代码优先确保你指定在一个数据模型类上的验证规则被强制实施。例如,下面的代码在SaveChanges方法被调用时将抛出一个异常,因为Movie一些必填属性的值未填,而且price为0(0这个值不在有效范围内)。
MovieDBContext db = new MovieDBContext();
Movie movie = new Movie();
movie.Title = "Gone with the Wind";
movie.Price = 0.0M;
db.Movies.Add(movie);
db.SaveChanges(); // <= Will throw server side validation exception
.NET Framework自动强制实施验证规则帮助使得你的应用程序更健壮。验证规则也能保证你不会忘记验证某项约束,和因疏忽导致向数据库写入错误数据。
下面列出Movie.cs文件修改后的一段完整的代码:
using System;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models {
public class Movie {
public int ID { get; set; }
[Required]
public string Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Required]
public string Genre { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[StringLength(5)]
public string Rating { get; set; }
}
public class MovieDBContext : DbContext {
public DbSet<Movie> Movies { get; set; }
}
}
在ASP.NET MVC中的验证错误后的提示信息
重新运行应用程序,并在地址栏中的网站后输入/Movies。
点击Create New链接到增加一个新电影记录的页面。用一些无效的值填写表单,然后点击Create按钮。
注解,为了支持用逗号(”,”)作为小数点的非英语文化的JQuery验证,你必须包含globalize.js文件,和你指定的cultures/globalize.cultures.js文件(这些文件可以从https://github.com/jquery/globalize获得)和JavaScript来使用Globalize.parseFloat。下面的代码展示Views\Movies\Edit.cshtml文件的修改后用”fr-FR”文化(法语文化,译者注)来实现:
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script src="~/Scripts/globalize.js"></script>
<script src="~/Scripts/globalize.culture.fr-FR.js"></script>
<script>
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
$(document).ready(function () {
Globalize.culture('fr-FR');
});
</script>
<script>
jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
//Use the Globalization plugin to parse the value
var val = $.global.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
</script>
}
注意表单如何自动使用一个红色边框来高亮显示包含无效数据的文本框,并且在每一个文本框后面显示一个合适验证错误提示信息。这个错误在客户端(使用JavaScript和JQuery)和服务器端(万一用户禁用JavaScript)同时执行。
一个实在的好处是你不必在MoviesController类或在Create.cshtml视图中为了启用验证UI来更改单独一行代码。在这篇教程中你在前面创建的控制器和视图自动采用验证规则,这些规则是你通过在Movie数据模型类的属性上使用验证特性来指定的。
你可能会注意到Title属性和Genre属性,在你提交表单(点击Create按钮)后才执行必填特性,或者向input字段输入文本信息,然后移除它。为了一个初始为空(例如在Create视图中的字段)的字段,这个字段只有必填特性,而没有其他验证特性,你能够做下面来触发验证:
1、按Tab键进入字段
2、输入一些文本信息
3、再按Tab键移出字段
4、按Tab键返回字段
5、移除文本信息
6、再按Tab键移出
按上述步骤顺序执行操作,没有点击提交按钮也会触发必填验证。不输入任何字段信息直接点击提交按钮将会触发客户端验证。直到没有客户端验证错误,表单数据才发送到服务器。通过在HTTP Post方法里设置也一个断点或者使用Fiddler工具或IE9的F12功能键的Developer工具(开发者工具,译者注),你能够测试这些验证。
在创建视图和创建Action方法(动作方法,译者注)怎样发生验证
你也许想知道在不修改控制器和视图的代码情况下,验证UI是怎样生成的。接下来列出的代码展示在MovieController类里的Create方法看起来是什么样。在本教程中,你早前怎么创建它们,它们仍然未被改变。
//
// GET: /Movies/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Movies/Create
[HttpPost]
public ActionResult Create(Movie movie)
{
if (ModelState.IsValid)
{
db.Movies.Add(movie);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
第一个(HTTP GET)Create功能方法显示原始的Create表单。第二个(HTTP POST)版本则以回发(post)方式处理表单。第二个Create方法(HTTP POST版本)调用ModelState.IsValid方法来检查电影信息是否被验证出错误。调用ModelState.IsValid方法会评估任何一个被应用到对象上的验证特性。如果对象存在验证错误,Create方法将重新显示表单。如果没有错误,方法将新的电影信息保存至数据库中。在我们所使用的电影例子中,当在客户端检测到它们存在验证错误时,表单就不再被提交到服务器;第二个Create方法也就不会被调用。如果你在浏览器上禁用了JavaScript功能,客户端验证将失效并且HTTP POST版本的Create方法调用ModelState.IsValid方法来检查电影是否存在验证错误。
你能够通过在HTTP POST版本的Create方法中设置断点来证明该方法从没被调用过,当服务器端验证方法检测到验证错误存在时,将不再提交表单数据。如果你在浏览器中禁用了JavaScript功能,然后带着验证错误提交表单,这时断点将会被命中。即使没有JavaScript功能,你仍然能够得到完整的验证功能。下面这张图片告诉你该如何在IE浏览器中禁用JavaScript功能。
下面这张图片告诉你该如何在火狐浏览器中禁用JavaScript功能。
下面这张图片告诉你该如何在谷歌的Chrome浏览器中禁用JavaScript功能。
下面是在本教程里先前你搭建(scaffold是支架、脚手架的意思)的Create.cshtml视图模版页面。该页面既被上面所展示的行动(action)方法用来显示原始表单,也被用来在一个错误事件中重新显示表单。
@model MvcMovie.Models.Movie
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"></script>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.ReleaseDate)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ReleaseDate)
@Html.ValidationMessageFor(model => model.ReleaseDate)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Genre)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Genre)
@Html.ValidationMessageFor(model => model.Genre)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Rating)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Rating)
@Html.ValidationMessageFor(model => model.Rating)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
注意查看代码是如何使用Html.EditorFor帮助器来为每个Movie属性输出<input>元素标签。接下来的这个帮助器是一个调用Html.ValidationMessageFor帮助器的方法。这两个帮助器方法作用于从控制器传递给视图的数据模型对象(在这个例子中,是一个Movie对象)。这两个帮助器方法根据情况自动搜索给数据模型和错误提示信息指定的验证特性。
这个方法真正令人愉快的是控制器和Create视图模板都不知道任何实际被执行的验证规则或者有关指定错误的提示信息。验证规则和错误的字符串仅仅在Movie类中被指出。这些相同的验证规则会被自动地应用到Edit视图以及其他你可能会创建出来的视图模版中来编辑你的数据模型。
如果以后你想要更改验证逻辑,那么你能够这么做:在一个恰当的地方给数据模型(本例中是Movie类)添加验证特性。你不必担心应用程序不同地方执行的验证规则不一致——所有验证逻辑在一个地方定义然后到处使用。这样可保持代码清爽,并使代码易于维护和改进。并且这意味着你将完全忠于DRY原则。
给Movie模型添加格式化
打开Movie.cs文件并检查Movie类。System.ComponentModel.DataAnnotations命名空间除了内置验证特性集之外,还提供了格式化特性。我们已经应用了一个数据类型(DataType)枚举值给release date和price字段。下面的代码展示带有合适的DisplayFormat特性的ReleaseDate和Price属性。
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[DataType(DataType.Currency)]
public decimal Price { get; set; }
数据类型特性不是验证特性,它们是被用来通知视图引擎如何渲染HTML页面的。在上面的例子中,DataType.Date特性仅仅显示电影日期数据的日期部分,而不带有时间部分。再例如,下面的数据类型特性也没有验证数据的格式:
[DataType(DataType.EmailAddress)]
[DataType(DataType.PhoneNumber)]
[DataType(DataType.Url)]
上面列出的特性仅仅为了视图引擎提供线索来格式化数据(提供特性就像给URL提供a标签和给email提供<a href=mailto:EmailAddress.com>一样。你能够使用正则表达式(RegularExpression)特性来验证数据的格式。
另一个使用数据类型特性的方法,你可以明确地设置一个数据字符串格式的值。下面的代码展示带有一个日期字符串格式(字符串格式被命名为”d”)的发行日期(release date)属性。你可以使用这个特性来指出你不想要时间作为发行日期的一部分。
[DisplayFormat(DataFormatString = "{0:d}")]
public DateTime ReleaseDate { get; set; }
下面的代码用货币形式来格式化价格属性。
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal Price { get; set; }
下面展示出完整的Movie类代码。
public class Movie {
public int ID { get; set; }
[Required]
public string Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Required]
public string Genre { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[StringLength(5)]
public string Rating { get; set; }
}
运行应用程序,并浏览Movies控制器生成的页面。发行日期和价格已经被格式化好了。下面的图片显示用”fr-FR”(法语,译者注)作为语言文化的发行日期和价格。
下面的图片显示相同的数据,但是用默认的语言文化(美国英语)。
在本系列的下一节里,我们将回顾整个应用程序,并且给自动生成的Details和Delete方法做一些改进。
By Rick Anderson, Rick Anderson works as a programmer writer for Microsoft, focusing on ASP.NET MVC, Windows Azure and Entity Framework. You can follow him on twitter via @RickAndMSFT.