添加验证
作者: 里克·安德森
备注
本教程的更新版本是使用最新版本的 Visual Studio 提供的。 新教程使用 ASP.NET Core MVC,这为本教程提供了 许多 改进。
本教程介绍具有控制器和视图的 ASP.NET Core MVC。 Razor Pages 是 ASP.NET Core 中的一种新替代方法,它是一种基于页面的编程模型,使生成 Web UI 更加轻松高效。 建议先尝试 Razor 页面教程,再使用 MVC 版本。 Razor 页面教程:
- 易于关注。
- 涵盖更多功能。
- 是新应用开发的首选方法。
在本部分中,你将向模型添加验证逻辑 Movie
,并确保每当用户尝试使用应用程序创建或编辑电影时,都会强制实施验证规则。
ASP.NET MVC 的核心设计原则之一是 DRY (“不要重复自己”)。 ASP.NET MVC 鼓励你仅指定一次功能或行为,然后将其反映在应用程序中的任何地方。 这减少了编写所需的代码量,并使你编写的代码容易出错且更易于维护。
ASP.NET MVC 和 Entity Framework Code First 提供的验证支持是 DRY 原则在操作中的一个很好的示例。 可以在一个位置(在模型类中)声明性地指定验证规则,并在应用程序中的任何地方强制实施这些规则。
让我们看看如何在电影应用程序中利用此验证支持。
首先,将一些验证逻辑添加到 Movie
类。
打开 Movie.cs 文件。 请注意命名空间 System.ComponentModel.DataAnnotations
不包含 System.Web
。 DataAnnotations 提供一组内置的验证属性,你可以以声明方式应用于任何类或属性。 (它还包含格式设置属性,如 有助于设置格式且不提供任何验证的 DataType 。
现在更新Movie
类以利用内置Required
、StringLength
RegularExpression 和Range
验证属性。 将 Movie
类替换为以下代码:
public class Movie
{
public int ID { get; set; }
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
[StringLength(5)]
public string Rating { get; set; }
}
该 StringLength
属性设置字符串的最大长度,并且它会对数据库设置此限制,因此数据库架构将发生更改。 右键单击服务器资源管理器中的 Movies 表,然后单击“打开表定义”:
在上图中,可以看到所有字符串字段都设置为 NVARCHAR (MAX)。 我们将使用迁移来更新架构。 生成解决方案,然后打开程序包管理器控制台窗口并输入以下命令:
add-migration DataAnnotations
update-database
此命令完成后,Visual Studio 将打开定义具有指定DataAnnotations
名称的新DbMigration
派生类的类文件,并在方法中Up
可以看到更新架构约束的代码:
public override void Up()
{
AlterColumn("dbo.Movies", "Title", c => c.String(maxLength: 60));
AlterColumn("dbo.Movies", "Genre", c => c.String(nullable: false, maxLength: 30));
AlterColumn("dbo.Movies", "Rating", c => c.String(maxLength: 5));
}
该 Genre
字段不再可为空(即必须输入值)。 该 Rating
字段的最大长度为 5, Title
最大长度为 60。 最小长度为 3, Title
范围 Price
未创建架构更改。
检查电影架构:
字符串字段显示新的长度限制, Genre
不再显示为可为 null。
验证特性指定要对应用这些特性的模型属性强制执行的行为。 Required
和 MinimumLength
特性表示属性必须有值;但用户可输入空格来满足此验证。 RegularExpression 属性用于限制可以输入的字符。 在上述代码中,Genre
和 Rating
仅可使用字母(禁用空格、数字和特殊字符)。 Range
特性将值限制在指定范围内。 StringLength
特性使你能够设置字符串属性的最大长度,以及可选的最小长度。 值类型(如 decimal, int, float, DateTime
)本质上是必需的,不需要属性 Required
。
Code First 可确保在应用程序保存数据库中的更改之前强制执行对模型类指定的验证规则。 例如,下面的代码会在调用方法时SaveChanges
引发 DbEntityValidationException 异常,因为缺少几个必需的Movie
属性值:
MovieDBContext db = new MovieDBContext();
Movie movie = new Movie();
movie.Title = "Gone with the Wind";
db.Movies.Add(movie);
db.SaveChanges(); // <= Will throw server side validation exception
上面的代码引发以下异常:
一个或多个实体的验证失败。 有关更多详细信息,请参阅“EntityValidationErrors”属性。
通过 .NET Framework 自动强制实施验证规则有助于使应用程序更加可靠。 同时它能确保你无法忘记验证某些内容,并防止你无意中将错误数据导入数据库。
运行应用程序并导航到 /Movies URL。
单击“新建”链接添加新电影。 使用无效值填写表单。 当 jQuery 客户端验证检测到错误时,会显示一条错误消息。
备注
若要支持对使用逗号(“,”)的非英语区域设置进行 jQuery 验证(对于小数点),必须包括 NuGet 全球化,如本教程前面所述。
请注意,表单如何自动使用红色边框颜色突出显示包含无效数据的文本框,并在每个文本框旁边发出了相应的验证错误消息。 客户端(使用 JavaScript 和 jQuery)和服务器端(若用户禁用 JavaScript)都必定会遇到这些错误。
真正的好处是,无需更改类或 Create.cshtml 视图中的单个代码MoviesController
行才能启用此验证 UI。 在本教程前面创建的控制器和视图会自动选取验证规则,这些规则是通过在 Movie
模型类的属性上使用验证特性所指定的。 使用 Edit
操作方法测试验证后,即已应用相同的验证。
存在客户端验证错误时,不会将表单数据发送到服务器。 可以通过使用 fiddler 工具或 IE F12 开发人员工具将断点放在 HTTP Post 方法中来验证这一点。
你可能想知道在不对控制器或视图中的代码进行任何更新的情况下,验证 UI 是如何生成的。 下一个列表显示 Create
类中 MovieController
的方法的外观。 与在本教程前面创建它们的方式没有变化。
public ActionResult Create()
{
return View();
}
// POST: /Movies/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Title,ReleaseDate,Genre,Price,Rating")] 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,则禁用客户端验证,HTTP POST Create
方法将检查 ModelState.IsValid
电影是否有任何验证错误。
可以在 HttpPost Create
方法中设置断点,并验证方法从未被调用,客户端验证在检测到存在验证错误时不会提交表单数据。 如果在浏览器中禁用 JavaScript,然后提交错误的表单,将触发断点。 在没有 JavaScript 的情况下仍然可以进行完整的验证。 下图显示了如何在 Internet Explorer 中禁用 JavaScript。
以下图片显示如何在 FireFox 浏览器中禁用 JavaScript。
以下图片显示如何在 Chrome 浏览器中禁用 JavaScript。
下面是在本教程前面搭建基架的 Create.cshtml 视图模板。 以上所示的操作方法使用它来显示初始表单,并在发生错误时重新显示此表单。
@model MvcMovie.Models.Movie
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
</div>
@*Fields removed for brevity.*@
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
请注意代码如何使用 Html.EditorFor
帮助程序为每个属性输出 <input>
元素 Movie
。 此帮助程序旁边是对帮助程序方法的 Html.ValidationMessageFor
调用。 这两个帮助程序方法适用于控制器传递给视图的模型对象(在本例中为 Movie
对象)。 它们会自动查找模型中指定的验证属性,并根据需要显示错误消息。
此方法真正好的一点是:无论是控制器还是 Create
视图模板都不知道强制实施的实际验证规则或显示的特定错误消息。 仅可在 Movie
类中指定验证规则和错误字符串。 这些相同的验证规则自动应用于 Edit
视图和可能创建用于编辑模型的任何其他视图模板。
如果以后要更改验证逻辑,可以通过向模型添加验证属性(在本示例中, movie
类)来完全在一个位置执行此操作。 无需担心对应用程序的不同部分所强制执行规则的方式不一致 - 所有验证逻辑都将定义在一个位置并用于整个应用程序。 这使代码非常简洁,并且更易于维护和改进。 这意味着你将充分遵守 DRY 原则。
打开 Movie.cs 文件并检查 Movie
类。 除了一组内置的验证特性,System.ComponentModel.DataAnnotations
命名空间还提供格式特性。 我们已经在发布日期和价格字段中应用了 DataType
枚举值。 以下代码显示具有适当 DataType
特性的 ReleaseDate
和 Price
属性。
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[DataType(DataType.Currency)]
public decimal Price { get; set; }
DataType 属性仅提供视图引擎设置数据格式的提示(并提供 URL 和<a href="mailto:EmailAddress.com">
电子邮件等<a>
属性)。 可以使用 RegularExpression 属性来验证数据的格式。 DataType 属性用于指定比数据库内部类型更具体的数据类型,它们不是验证属性。 在此示例中,我们只想跟踪日期,而不是日期和时间。 DataType 枚举为许多数据类型提供,例如 Date、Time、PhoneNumber、Currency、EmailAddress 等。 应用程序还可通过 DataType
特性自动提供类型特定的功能。 例如,mailto:
可以为 DataType.EmailAddress 创建链接,并且可为支持 HTML5 的浏览器中的 DataType.Date 提供日期选择器。 DataType 属性发出 HTML 5 数据(发音的数据短划线)属性,HTML 5 浏览器可以理解这些属性。 DataType 属性不提供任何验证。
DataType.Date
不指定显示日期的格式。 默认情况下,数据字段根据服务器 CultureInfo 的默认格式显示。
DisplayFormat
特性用于显式指定日期格式:
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
该 ApplyFormatInEditMode
设置指定在文本框中显示值以供编辑时,还应应用指定的格式。 (你可能不希望对某些字段使用,例如,对于货币值,你可能不希望文本框中的货币符号进行编辑。
可以单独使用 DisplayFormat 属性,但通常最好也使用 DataType 属性。 该 DataType
属性传达 数据的语义 ,而不是如何在屏幕上呈现数据,并提供以下好处,你无法获取 DisplayFormat
:
- 浏览器可以启用 HTML5 功能(例如显示日历控件、适合区域设置的货币符号、电子邮件链接等)。
- 默认情况下,浏览器将根据区域设置使用正确的格式呈现数据。
- DataType 属性可以让 MVC 选择正确的字段模板来呈现数据(如果由自身使用字符串模板,则 DisplayFormat)。 有关详细信息,请参阅 Brad Wilson 的 ASP.NET MVC 2 模板。 (虽然为 MVC 2 编写,但本文仍适用于当前版本的 ASP.NET MVC。
如果将 DataType
属性与日期字段一起使用,则还必须指定 DisplayFormat
该属性,以确保该字段在 Chrome 浏览器中正确呈现。 有关详细信息,请参阅 此 StackOverflow 线程。
备注
jQuery 验证不适用于 Range 属性和 DateTime。 例如,以下代码将始终显示客户端验证错误,即便日期在指定的范围内:
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]
需要禁用 jQuery 日期验证才能将 Range 属性与 DateTime 一起使用。 在模型中编译硬日期通常不是很好的做法,因此不建议使用 Range 属性和 DateTime。
以下代码显示组合在一行上的特性:
public class Movie
{
public int ID { get; set; }
[Required,StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Display(Name = "Release Date"),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; }
[Required,StringLength(5)]
public string Rating { get; set; }
}
在本系列的下一部分中,我们将回顾应用程序,并对自动生成的 Details
和 Delete
方法进行一些改进。