8.1 DRY原则
在ASP.NET MVC中,有一条作为核心的原则,就是DRY(“Don’t Repeat Yourself,中文意思为:不要让开发者重复做同样的事情)原则。ASP.NET MVC提倡让开发者“一处定义、处处可用”。这样可以减少开发者的代码编写量,同时也更加便于代码的维护。
ASP.NET MVC与EF code-first提供的默认验证规则就是一个实现DRY原则的很好的例子。你也可以在模型类中显式地追加一个验证规则,然后在整个应用程序中都使用这个验证规则。
现在让我们来看一下怎样在我们的应用程序中追加一些验证规则。
8.2 在Movie模型中追加验证规则
首先,让我们在Movie类中追加一些验证规则。
打开Movie.cs文件,在文件的头部追加一条引用System.ComponentModel.DataAnnotations命名空间的using语句,代码如下所示。
- using System.ComponentModel.DataAnnotations;
这个System.ComponentModel.DataAnnotations命名空间是.NET Framework中的一个命名空间。它提供了很多内建的验证规则,你可以对任何类或属性显式指定这些验证规则。
现在让我们来修改Movie类,增加一些内建的Required(必须输入),StringLength(输入字符长度)与Range(输入范围)验证规则,代码如代码清单8-1所示。
代码清单8-1 在Movie类中追加内建的验证规则
- public class Movie
- {
- public int ID { get; set; }
- [Required(ErrorMessage = "必须输入标题")]
- public string Title { get; set; }
- [Required(ErrorMessage = "必须输入发行日期")]
- public DateTime ReleaseDate { get; set; }
- [Required(ErrorMessage = "必须指定种类")]
- public string Genre { get; set; }
- [Required(ErrorMessage = "必须输入票价")]
- [Range(1, 100, ErrorMessage = "票价必须在1元到100元之间")]
- public decimal Price { get; set; }
- [StringLength(5,ErrorMessage = "最多允许输入五个字符")]
- public string Rating { get; set; }
- }
上述这些验证属性指定了我们想要强加给模型中各属性的验证规则。Required属性表示必须要指定一个属性值,在上例中,一个有效的电影信息必须含有标题,发行日期,种类与票价信息。Range属性表示属性值必须在一段范围之间。StringLength属性表示一个字符串属性的最大长度或最短长度。
EF code-first在将一条数据保存到数据库中之前首先使用你对模型类指定的验证规则来对这条数据进行有效性检查。例如,在以下代码中,当程序调用SaveChanges方法时将抛出一个异常(也称例外),因为数据并不满足Movie属性的必须输入条件,同时票价属性的值为0,不在指定的允许范围内(1-100)。
- MovieDBContext db = new MovieDBContext();
- Movie movie = new Movie();
- movie.Title = "大笑江湖;
- movie.Price = 0.0M;
- db.Movies.Add(movie);
- db.SaveChanges(); // 这里将抛出一个校验异常
通过Entity Framework来自动实现验证规则检查可以让我们的应用程序变得更强健。它也确保我们不会由于忘了实施数据验证而使得一些无效数据保存到数据库中。
代码清单8-2为现在Movie.cs文件中的完整代码。
代码清单8-2 Movie.cs文件中的完整代码
- using System.Data.Entity;
- using System.Data.Entity.ModelConfiguration;
- using System.ComponentModel.DataAnnotations;
- namespace MvcMovie.Models
- {
- public class Movie
- {
- public int ID { get; set; }
- [Required(ErrorMessage = "必须输入标题")]
- public string Title { get; set; }
- [Required(ErrorMessage = "必须输入发行日期")]
- public DateTime ReleaseDate { get; set; }
- [Required(ErrorMessage = "必须指定种类")]
- public string Genre { get; set; }
- [Required(ErrorMessage = "必须输入票价")]
- [Range(1, 100, ErrorMessage = "票价必须在1元到100元之间")]
- public decimal Price { get; set; }
- [StringLength(5,ErrorMessage = "最多允许输入五个字符")]
- public string Rating { get; set; }
- }
- public class MovieDBContext : DbContext
- {
- public DbSet<Movie> Movies { get; set; }
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- modelBuilder.Entity<Movie>().Property(p =>
- p.Price).HasPrecision(18, 2);
- }
- }
- }
8.3 ASP.NET MVC中的验证错误UI(用户界面)
现在让我们运行我们的应用程序,并在地址栏中输入“http://localhost:xx/Movies”。
在电影清单画面中点击追加按钮打开追加电影画面。在该画面中的表单中填入一些无效的属性值,然后点击追加按钮。如图8-1所示。
图8-1 ASP.NET MVC中的验证错误UI
请注意表单自动使用了一个背景颜色来高亮显示包含了无效数据的文本框,并且在每个文本框的旁边显示验证错误信息。使用的错误信息文字正是我们在前面代码中所指定的验证错误的错误信息文字。这个验证错误既可以由客户端引发(使用JavaScript脚本),也可以由服务器端引发(当用户禁止使用JavaScript脚本时)。
这种处理方法是非常不错的,因为我们不再需要为了显示错误信息文字而在MoviesController类或Create.cshtml视图文件中书写不必要的代码。我们之前创建的控制器与视图将自动实施验证规则与显示验证错误信息文字。
8.4 在Create视图(追加电影视图)与Create方法内部是如何实现验证的
也许有的读者会问,既然我们没有追加任何显示错误信息提示的代码,那么我们的控制器或视图内部是如何生成这个显示错误信息提示的画面的。首先我们将MovieController类中的代码显示如下,在我们在Movie类中追加了验证规则后,我们并没有修改这个类中的任何代码。
- //
- // GET: /Movies/Create
- public ActionResult Create()
- {
- return View();
- }
- //
- // POST: /Movies/Create
- [HttpPost]
- public ActionResult Create(Movie newMovie)
- {
- if (ModelState.IsValid)
- {
- db.Movies.Add(newMovie);
- db.SaveChanges();
- return RedirectToAction("Index");
- }
- else
- {
- return View(newMovie);
- }
- }
第一个方法返回追加电影视图。在第二个方法中对追加电影视图中的表单的提交进行处理。该方法中的ModelState.IsValid属性用来判断是否提交的电影数据中包含有任何没有通过数据验证的无效数据。如果存在无效数据,Create方法重新返回追加电影视图。如果数据全部有效,则将该条数据保存到数据库中。
我们之前创建的使用支架模板的Create.cshtml视图模板中的代码显示如下,在首次打开追加电影视图与数据没有通过验证时,Create方法中返回的视图都是使用的这个视图模板。
- @model MvcMovie.Models.Movie
- @{
- ViewBag.Title = "追加电影信息";
- }
- <h2>追加电影信息</h2>
- <script src="@Url.Content("~/Scripts/jquery.validate.min.js")"
- type="text/javascript"></script>
- <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"
- type="text/javascript"></script>
- @using (Html.BeginForm()) {
- @Html.ValidationSummary(true)
- <fieldset>
- <legend>电影</legend>
- <div class="editor-label">
- 标题
- </div>
- <div class="editor-field">
- @Html.EditorFor(model => model.Title)
- @Html.ValidationMessageFor(model => model.Title)
- </div>
- <div class="editor-label">
- 发行日期
- </div>
- <div class="editor-field">
- @Html.EditorFor(model => model.ReleaseDate)
- @Html.ValidationMessageFor(model => model.ReleaseDate)
- </div>
- <div class="editor-label">
- 种类
- </div>
- <div class="editor-field">
- @Html.EditorFor(model => model.Genre)
- @Html.ValidationMessageFor(model => model.Genre)
- </div>
- <div class="editor-label">
- 票价
- </div>
- <div class="editor-field">
- @Html.EditorFor(model => model.Price)
- @Html.ValidationMessageFor(model => model.Price)
- </div>
- <p>
- <input type="submit" value="追加" />
- </p>
- </fieldset>
- }
- <div>
- @Html.ActionLink("返回电影列表", "Index")
- </div>
请注意在这段代码中使用了许多Html.EditorFor帮助器来为Movie类的每个属性输出一个输入文本框。在每个Html.EditorFor帮助器之后紧跟着一个Html.ValidationMessageFor帮助器。这两个帮助器将与从控制器传入的模型类的对象实例(在本示例中为Movie对象的一个实例)结合起来,自动寻找指定给模型的各个验证属性,然后显示对应的验证错误信息。
这种验证体制的好处是在于控制器和Create视图(追加电影视图)事先都即不知道实际指定的验证规则,也不知道将会显示什么验证错误信息。验证规则和错误信息只在Movie类中被指定。
如果我们之后想要改变验证规则,我们也只要在一处地方进行改变就可以了。我们不用担心整个应用程序中存在验证规则不统一的问题,所有的验证规则都可以集中在一处地方进行指定,然后在整个应用程序中使用这些验证规则。这将使我们的代码更加清晰明确,更加具有可读性、可维护性与可移植性。这将意味着我们的代码是真正符合DRY原则(一处指定,到处可用)的。
在下一节中,作为结尾部分,我们将介绍如何修改与删除数据,同时介绍如何显示一条数据的细节信息。