将验证逻辑添加到电影模型
作者: 里克·安德森
注意
此处提供了本教程的更新版本,它使用 ASP.NET MVC 5 和 Visual Studio 2013。 它更安全,更易于遵循并演示更多功能。
在本部分中,你将向模型添加验证逻辑 Movie
,并确保每当用户尝试使用应用程序创建或编辑电影时,都会强制实施验证规则。
使事情保持干燥
ASP.NET MVC 的核心设计原则之一是 DRY(“不要重复自己”)。 ASP.NET MVC 鼓励你仅指定一次功能或行为,然后将其反映在应用程序中的任何地方。 这减少了编写所需的代码量,并使你编写的代码容易出错且更易于维护。
ASP.NET MVC 和 Entity Framework Code First 提供的验证支持是 DRY 原则在操作中的一个很好的示例。 可以在一个位置(在模型类中)声明性地指定验证规则,并在应用程序中的任何地方强制实施这些规则。
让我们看看如何在电影应用程序中利用此验证支持。
将验证规则添加到电影模型
首先,将一些验证逻辑添加到 Movie
类。
打开 Movie.cs 文件。 在引用System.ComponentModel.DataAnnotations
命名空间的文件顶部添加语句using
:
using System.ComponentModel.DataAnnotations;
请注意命名空间不包含 System.Web
。 DataAnnotations 提供一组内置的验证属性,你可以以声明方式应用于任何类或属性。
现在更新Movie
类以利用内置Required
StringLength
属性和Range
验证属性。 使用以下代码作为应用属性的位置的示例。
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; }
}
运行应用程序,你将再次收到以下运行时错误:
自创建数据库以来,支持“MovieDBContext”上下文的模型发生了更改。请考虑使用Code First 迁移来更新数据库(https://go.microsoft.com/fwlink/?LinkId=238269)。
我们将使用迁移来更新架构。 生成解决方案,然后打开程序包管理器控制台窗口并输入以下命令:
add-migration AddDataAnnotationsMig
update-database
此命令完成后,Visual Studio 将打开类文件,该文件定义 DbMigration
具有指定名称的新派生类(AddDataAnnotationsMig),并在方法中 Up
可以看到更新架构约束的代码。 字段Title
Genre
不再可为 null(即,必须输入值),Rating
并且该字段的最大长度为 5。
验证特性指定要对应用这些特性的模型属性强制执行的行为。 该Required
属性指示属性必须具有值;在此示例中,电影必须具有值Genre
Title
ReleaseDate
Price
才能有效。 Range
特性将值限制在指定范围内。 StringLength
特性使你能够设置字符串属性的最大长度,以及可选的最小长度。 默认情况下,内部类型(如 decimal, int, float, DateTime
)是必需的,不需要属性 Required
。
Code First 可确保在应用程序保存数据库中的更改之前强制执行对模型类指定的验证规则。 例如,下面的代码将在调用方法时 SaveChanges
引发异常,因为缺少几个必需的 Movie
属性值,并且价格为零(超出有效范围)。
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 中的验证错误 UI
重新运行应用程序并导航到 /Movies URL。
单击“新建”链接添加新电影。 使用一些无效值填写表单,然后单击“ 创建 ”按钮。
注意
若要支持对使用逗号(“,”)的非英语区域设置的 jQuery 验证(“,”),必须包含globalize.js和特定区域性/globalize.cultures.js文件(from https://github.com/jquery/globalize )和 JavaScript 才能使用Globalize.parseFloat
。 以下代码显示用于处理“fr-FR”区域性的 Views\Movies\Edit.cshtml 文件的修改:
@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)都必定会遇到这些错误。
真正的好处是,无需更改类或 Create.cshtml 视图中的单个代码MoviesController
行才能启用此验证 UI。 在本教程前面创建的控制器和视图会自动选取验证规则,这些规则是通过在 Movie
模型类的属性上使用验证特性所指定的。
你可能已经注意到属性 Title
,并且 Genre
,在提交表单(点击 “创建 ”按钮)或输入文本并将其删除之前,才会强制实施所需的属性。 对于最初为空(例如“创建”视图上的字段)且只有所需属性且没有其他验证属性的字段,可以执行以下操作来触发验证:
- 按 Tab 键进入字段。
- 输入一些文本。
- 退出选项卡。
- Tab 返回字段。
- 删除文本。
- 退出选项卡。
上述序列将触发所需的验证,而无需点击“提交”按钮。 只需点击提交按钮而不输入任何字段将触发客户端验证。 存在客户端验证错误时,不会将表单数据发送到服务器。 可以通过将断点放在 HTTP Post 方法中或使用 fiddler 工具 或 IE 9 F12 开发人员工具来测试此问题。
在“创建视图”和“创建操作”方法中如何进行验证
你可能想知道在不对控制器或视图中的代码进行任何更新的情况下,验证 UI 是如何生成的。 下一个列表显示 Create
类中 MovieController
的方法的外观。 与在本教程前面创建它们的方式没有变化。
//
// 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
操作方法显示初始的“创建”表单。 第二个 ([HttpPost]
) 版本处理表单发布。 第二个 Create
方法(HttpPost
版本)调用 ModelState.IsValid
以检查电影是否有任何验证错误。 调用此方法将评估已应用于对象的任何验证特性。 如果对象有验证错误,则 Create
方法会重新显示此表单。 如果没有错误,此方法则将新电影保存在数据库中。 在我们的电影示例中,当客户端检测到验证错误时,表单不会发布到服务器;从不调用第二Create
种方法。 如果在浏览器中禁用 JavaScript,则会禁用客户端验证,并调用 ModelState.IsValid
HTTP POST Create
方法来检查电影是否有任何验证错误。
可以在 HttpPost Create
方法中设置断点,并验证方法从未被调用,客户端验证在检测到存在验证错误时不会提交表单数据。 如果在浏览器中禁用 JavaScript,然后提交错误的表单,将触发断点。 在没有 JavaScript 的情况下仍然可以进行完整的验证。 下图显示了如何在 Internet Explorer 中禁用 JavaScript。
以下图片显示如何在 FireFox 浏览器中禁用 JavaScript。
下图显示了如何使用 Chrome 浏览器禁用 JavaScript。
下面是在本教程前面搭建基架的 Create.cshtml 视图模板。 以上所示的操作方法使用它来显示初始表单,并在发生错误时重新显示此表单。
@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
帮助程序为每个属性输出 <input>
元素 Movie
。 此帮助程序旁边是对帮助程序方法的 Html.ValidationMessageFor
调用。 这两个帮助程序方法适用于控制器传递给视图的模型对象(在本例中为 Movie
对象)。 它们会自动查找模型中指定的验证属性,并根据需要显示错误消息。
这种方法非常好的是,控制器和创建视图模板都不知道强制执行的实际验证规则或显示的特定错误消息。 仅可在 Movie
类中指定验证规则和错误字符串。 这些相同的验证规则会自动应用于“编辑”视图和可能创建用于编辑模型的任何其他视图模板。
如果以后要更改验证逻辑,可以通过向模型添加验证属性(在本示例中, movie
类)来完全在一个位置执行此操作。 无需担心对应用程序的不同部分所强制执行规则的方式不一致 - 所有验证逻辑都将定义在一个位置并用于整个应用程序。 这使代码非常简洁,并且更易于维护和改进。 这意味着对 DRY 原则的完全遵守。
向电影模型添加格式
打开 Movie.cs 文件并检查 Movie
类。 除了一组内置的验证特性,System.ComponentModel.DataAnnotations
命名空间还提供格式特性。 我们已经在发布日期和价格字段中应用了 DataType
枚举值。 以下代码显示具有适当 DisplayFormat
特性的 ReleaseDate
和 Price
属性。
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[DataType(DataType.Currency)]
public decimal Price { get; set; }
这些 DataType
属性不是验证属性,它们用于告知视图引擎如何呈现 HTML。 在上面的示例中,该 DataType.Date
属性仅将电影日期显示为日期,而不显示时间。 例如,以下 DataType
属性不验证数据的格式:
[DataType(DataType.EmailAddress)]
[DataType(DataType.PhoneNumber)]
[DataType(DataType.Url)]
上面列出的属性仅提供视图引擎设置数据格式的提示(并提供属性,例如 <> URL 属性和<用于电子邮件的 href=“mailto:EmailAddress.com”。> 可以使用 RegularExpression 属性来验证数据的格式。
可以使用特性的 DataType
替代方法,可以显式设置值 DataFormatString
。 以下代码显示了具有日期格式字符串(即“d”)的发布日期属性。 使用此选项可以指定不想在发布日期中计时。
[DisplayFormat(DataFormatString = "{0:d}")]
public DateTime ReleaseDate { 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
方法进行一些改进。