本节我们将为Movie类型增加逻辑验证,以确保任何用户试图使用该应用程序创建或编辑影片时执行验证规则。
Dry
ASP.NET MVC的核心原则之一是干爽(“不需要自己重复”)。ASP.NET MVC鼓励我们只指定一次功能或行为,然后在应用程序中到处使用。这种做法减少了需要编写的代码量,使你的代码编写不容易出错,且更容易维护。
ASP.NET MVC和Entity Framework代码先行为验证提供支持,是干爽原则应用的一个很好的例子。在一个模型的类中以声明方式指定验证规则,在整个应用程序中执行验证规则。
让我们来看看如何在电影程序中利用验证支持。
向Movie模型中添加验证规则
首先向Movie类中加入一些验证逻辑。
打开的Movie.cs文件。在文件的顶部添加using语句,引用命名空间:using System.ComponentModel.DataAnnotations;
请注意,命名空间中不包含的System.Web。DataAnnotations提供了内置的验证属性,你可以对任何类或属性应用。
现在修改Movie类,利用内置的Required,StringLength和Range验证属性。下面的代码是应用属性的例子:
public class Movie { public int ID { get; set; } [Required] public string Name { get; set; } public string Genra { get; set; } [Range(1,100)] public decimal Price { get; set; } public DateTime Date { 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命令,查看AddDataAnnotationsMig文件,其中的Up方法,可以看到Name字段不能为空,Rating属性最大长度为5.
public override void Up() { AlterColumn("dbo.Movies", "Name", c => c.String(nullable: false)); AlterColumn("dbo.Movies", "Rating", c => c.String(maxLength: 5)); }
验证属性指定应用到模型属性要执行的行为。Required属性表示属性不能为空,在本示例中,影片名称必须有值。Range范围属性限制在指定的范围内。StringLength属性可设定字符串的最大长度,以及最小长度(可选)。内建类型(如decimal,int,float,DateTime)默认情况下,并不需要Required属性。
代码先行确保应用程序将更改保存到数据库前你指定的模型类的验证规则被执行。例如下面的代码调用SaveChanges方法时将抛出一个异常,因为不能为空的片名属性值没有值,以及价格是零(不在有效范围内)。
MovieDbContext db = new MovieDbContext(); Movie movie = new Movie(); movie.Title = ""; movie.Price = 0.0M; db.Movies.Add(movie); db.SaveChanges();
.NET Framework自动执行验证规则,使我们的程序更加健壮。它可确保我们不要忘记验证东西,以及不经意间让错误的数据存到数据库中。
ASP.NET MVC中验证出错界面
运行程序,点击“新建”链接,新建一部影片。使用一些无效数据来填充表单,然后点击创建按钮。
请注意,表单自动使用红色边框的突出显示包含无效的数据的文本框,并在每一个旁边提示适当的验证错误消息。错误包括客户端(使用Javascript和Jquery)和服务器端(如果用户已禁用Javascript)。
一个真正的好处是,你并不需要改变MoviesController类或者Create.cshtml视图中的一行代码,就可以实现验证界面。在本系列前面创建的控制器和视图,自动使用我们指定的Movie模型类的属性上的验证属性的验证规则。
注意到的Name属性,Required属性没有被执行,知道提交表单(点击Create按钮),或在输入字段中输入文本并删除它。初始值为空(如创建视图中的字段)且只有required属性,没有其他的验证属性的字段,我们可以执行以下操作来触发验证:
1. Tab键进入该字段。
2. 输入一些文字。
3. Tab键移出。
4. 按Tab切换回进入该字段。
5.删除文本。
上述顺序将触发所要求的验证,不需要点击“提交”按钮。只要按“提交”按钮,无需进入任何字段,将触发客户端验证。若没有客户端验证错误,则表单数据发送到服务器。我们可以通过在Http Post方法设置断点或者使用Fiddler工具或IE9 F12开发人员工具来测试这点。
创建视图和创建方法中如何触发验证
我们可能想知道生成的控制器或视图中的代码没有任何更新的情况下验证界面是如何产生的。下一个清单显示的是在MovieController类Create方法。
// // GET: /Movies/Create public ActionResult Create() { return View(); } // // POST: /Movies/Create [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(Movie movie) { if (ModelState.IsValid) { db.Movies.Add(movie); db.SaveChanges(); return RedirectToAction("Index"); } return View(movie); }
第一个(HttpGet)Create的操作方法显示初始创建菜单。第二个([HttpPost])版本的操作方法负责处理post请求。第二个Create的方法(HttpPost版本)调用ModelState.IsValid,以检查是否有任何验证错误。调用该方法将检查任何已应用到对象属性上的验证。如果对象的Create方法验证错误,重新显示表单。如果没有错误,该方法在数据库保存新的电影。在我们的电影例子中,在客户端验证检测到的错误,表单将不会发送到服务器时,第二个Create方法不会被调用。如果我们在浏览器中禁用了Javascript,客户端验证被禁用,Http Post版本的Create方法调用ModelState.IsValid,以检查是否有任何验证错误。
我们可以在HttpPost Create方法中设置一个断点,验证该方法不会被调用,客户端验证发现错误时将不提交表单数据。如果我们在浏览器中禁用了Javascript,然后提交了有错误的表单,断点会命中。不支持Javascript的情况下,我们仍然可以得到充分验证。
请注意,代码是如何使用Html.EditorFor助手为每个Movie属性输出<input>元素的。也注意下Html.ValidationMessagerFor助手。这两个辅助方法,通过控制器传递给视图模型对象(在本例中是Movie对象)发生作用。它们自动寻找模型上指定的验证属性并显示验证错误信息。
控制器和Create视图模板不知道什么实际的验证规则正在执行或特定的错误消息提示,这种做法非常好。只需要在Movie类里指定验证规则和错误字符串,同样的验证规则会自动应用到编辑视图和任何其他视图模板,我们可以创建,标记我们的模型。
如果我们想更改验证逻辑,我们可以限定在一处地方(在这个例子中,Movie类),为模型添加验证属性。我们不需要担心应用程序的不同部分执行的规则不一致,所有的验证逻辑将被定义在一个地方,应用在各个地方。这样可以使代码很干净,而且很容易进行维护和改进。
为模型添加格式化信息
打开的Movie.cs的文件,查看的Movie类。System.ComponentModel.DataAnnotations命名空间中除了内置的验证属性,还提供了格式化属性。下面的代码显示了添加适当的DisplayFormat属性的Date和Price。
[DataType(DataType.Currency)] public decimal Price { get; set; } [DataType(DataType.Date)] public DateTime Date { get; set; }
DataType属性不是验证属性,它是用来告诉视图引擎如何生成Html。在上面的例子中,DataType.Date属性显示Date为日期,不包含时间。例如,下面的数据类型属性不验证数据格式
[DataType(DataType.EmailAddress)]
[DataType(DataType.PhoneNumber)]
[DataType(DataType.Url)]
上面列出的属性为视图引擎格式化数据提供一些参考(为url提供<a>以及为email提供<a href="mailto:EmailAddress.com">)。我们也可以使用正则表达式来验证格式化数据。
使用的DataType属性的另一种方法,我们可以显示设置DataFormatString值。下面的代码显示的日期格式字符串(即“D”)的发布日期属性。使用该功能,表明我们不想把时间作为日期的一部分。
[DisplayFormat(DataFormatString = "{0:d}")] public DateTime ReleaseDate { get; set; }
下面的代码将Price属性格式化为货币
[DisplayFormat(DataFormatString = "{0:c}")] public decimal Price { get; set; }
下节,我们将回顾应用程序,为自动生成的Details和Delete方法做出一些改进。