Skip to content

5. 视图及其模型

5.1. 简介

让我们回到 ASP.NET MVC 应用程序的架构:

在上一章中,我们探讨了 ASP.NET MVC 如何将请求信息 [1] 以模型的形式(该模型可能包含验证约束)呈现给操作 [2a]。该模型作为输入传递给操作,我们将其称为操作模型。 现在我们将重点关注操作最常见的返回结果—— [ViewResult]类型,它对应于视图V[3]及其配套的模型M[2c]。该模型将被称为视图模型V,请勿与我们刚刚研究过的操作模型混淆。前者是操作的输入,后者是操作的输出。

首先,让我们在同一解决方案中创建一个新的项目 [Example-03] [1],类型为基本的 ASP.NET MVC:

现在创建一个名为 [First] [2] 的控制器。该控制器生成的代码如下:


using System.Web.Mvc;
 
namespace Exemple_03.Controllers
{
  public class FirstController : Controller
  {
    public ActionResult Index()
    {
      return View();
    }
 
  }
}

  • 第 7–10 行:创建了一个 [Index] 操作。该 [Index] 方法的返回类型是 [ActionResult] 类,大多数可能的操作结果都继承自该类;
  • 第 9 行:[Controller] 类(第 5 行)的 [View] 方法返回 [ViewResult] 类型,该类型继承自 [ActionResult]。该方法支持多种重载形式。我们将探讨其中几种。主要形式如下:

Image

  • 第一个参数是视图的名称。如果省略该参数,则使用与生成 [ViewResult] 的操作同名的视图,该视图将在 [/Views/{controller}] 文件夹中查找,其中 {controller} 是控制器名称;
  • 第二个参数是视图模板。如果省略,则该视图没有模板。

下面的 [Index] 方法:


public ActionResult Index()
    {
      return View();
}

请求显示 [ /Views/First/Index.cshtml ] 视图。它没有向该视图传递任何模板。让我们创建 [1] [/Views/First] 文件夹:

然后在其内部创建 [Index] 视图 [2]:

我们在 [3] 中指定视图名称。视图在 [4] 中创建。生成的代码如下:


@{
    Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
 
    </div>
</body>
</html>

这属于标准 HTML,但第 1 至 3 行是 C# 代码。管理视图的程序称为视图引擎。它处理所有非 HTML 内容,并将其转换为 HTML。最终,这些内容将被发送至客户端。此处使用的视图引擎是 [Razor]。它允许您在视图中包含 C# 代码。 [Razor] 会解析这些 C# 代码并据此生成 HTML。以下是在视图中包含 C# 代码的一些基本规则:

  • 当遇到 @ 字符时(第 1 行),程序会从 HTML 模式切换到 C# 模式。如果该字符引入的是代码块,则使用大括号(第 1 行和第 3 行);如果引入的是需要获取其值的变量,只需写 @variable;
  • 当遇到 < 字符时(第 5 行),会从 C# 切换回 HTML。有时您可能需要强制进行此切换,特别是在页面中包含不含 HTML 标签的纯文本时。这种情况下,请使用 <text> 标签插入文本:<text>此处为纯文本</text>

上文第 2 行表明 [Index] 视图没有母版页。

让我们按以下方式修改该视图:


@{
  Layout = null;
  string vue = "Index";
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Index</title>
</head>
<body>
  <div>
    <h3>Vue @vue</h3>
  </div>
</body>
</html>

  • 第 3 行:定义一个 C# 变量;
  • 第15行:显示该变量的值。

现在让我们请求 URL [/First/Index]:

Image

收到的 HTML 代码如下:

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Index</title>
</head>
<body>
  <div>
    <h3>Vue Index</h3>
  </div>
</body>
</html>

这是一个纯 HTML 文档。所有 C# 代码均已被移除。

5.2. 使用 [ViewBag] 将信息传递给视图

我们创建一个名为 [Action01] 的新操作,并与 [Action01.cshtml] 视图相关联:

[Action01] 操作如下:


    // Action01
    public ViewResult Action01()
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return View();
}

  • 第 4 行:我们使用了控制器中的 [ViewBag] 属性。这是一个动态对象,可以像第 4 行所示那样向其添加属性。该对象的一个关键特征是视图也可以访问它。因此,它是一种向视图传递信息的方式;
  • 第 5 行:请求该操作的默认视图。即 [/First/Action01.cshtml] 视图。未向其传递任何模型。

[Action01.cshtml] 视图如下:


@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action01</title>
</head>
<body>
  <div>
    <h4>@ViewBag.info</h4>
  </div>
</body>
</html>

  • 第 14 行:显示 [ViewBag.info] 属性。

让我们进行测试。我们请求 URL [/First/Action01]:

Image

5.3. 使用强类型模型向视图传递信息

前一种方法的缺点在于无法在执行前检测错误。因此,如果视图 [Action01.cshtml] 使用以下代码


<h4>@ViewBag.Info</h4>

将会发生错误,因为 [Info] 属性并不存在。由 [Action01] 操作生成的属性名为 [info]。因此,我们可以使用强类型模型来避免此问题。

在前面讨论的一个示例中,该操作如下:


    // Action10
    public ContentResult Action10(ActionModel03 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("email={0}, jour={1}, info1={2}, info2={3}, info3={4}, erreurs={5}",
        modèle.Email, modèle.Jour, modèle.Info1, modèle.Info2, modèle.Info3, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

[Action10] 操作方法此前将六项信息(Email、Day、Info1、Info2、Info3、errors)作为字符串传递给客户端。我们将把这些信息传递给视图模型 [ViewModel01]。由于该模型使用来自 [ActionModel03] 的信息,因此我们将使其继承自该类。

首先,我们将 [Example-02] 项目中的 [ActionModel03] 复制到当前的 [Example-03] 项目中:

并将它的命名空间修改为与 [Example-03] 项目一致:


using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
  public class ActionModel03
  {
    [Required(ErrorMessage = "Le paramètre email est requis")]
    [EmailAddress(ErrorMessage = "Le paramètre email n'a pas un format valide")]
    public string Email { get; set; }
 
    [Required(ErrorMessage = "Le paramètre jour est requis")]
    [RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramètre jour doit avoir 1 ou 2 chiffres")]
    public string Jour { get; set; }
 
    [Required(ErrorMessage = "Le paramètre info1 est requis")]
    [MaxLength(4, ErrorMessage = "Le paramètre info1 ne peut avoir plus de 4 caractères")]
    public string Info1 { get; set; }
 
    [Required(ErrorMessage = "Le paramètre info2 est requis")]
    [MinLength(2, ErrorMessage = "Le paramètre info2 ne peut avoir moins de 2 caractères")]
    public string Info2 { get; set; }
 
    [Required(ErrorMessage = "Le paramètre info3 est requis")]
    [MinLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    [MaxLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    public string Info3 { get; set; }
  }
}

  • 第 2 行:新的命名空间;

然后我们创建 [ViewModel01] 类:

[ViewModel01] 的代码如下:


namespace Exemple_03.Models
{
  public class ViewModel01 : ActionModel03
  {
    public string Erreurs { get; set; }
  }
}

  • 第 3 行:该类继承自 [ActionModel03],因此也继承了 [Email、Day、Info1、Info2、Info3] 这些属性;
  • 第 5 行:我们向其中添加了 [Errors] 属性。

现在我们编写操作 [Action02],该操作:

  • 将操作模型 [ActionModel03] 作为输入;
  • 并返回视图模型 [ViewModel01]。

其代码如下:


    // Action02
    public ViewResult Action02(ActionModel03 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      return View(new ViewModel01(){Email=modèle.Email, Jour=modèle.Jour, Info1=modèle.Info1, Info2=modèle.Info2, Info3=modèle.Info3, Erreurs=erreurs});
}

  • 第 1 行:[Action02] 接收操作模型 [ActionModel03]。它返回类型为 [ViewResult] 的结果;
  • 第 4 行:与操作模型 [ActionModel03] 相关的错误被聚合到字符串 [errors] 中。[getErrorMessagesFor] 方法已在第 60 页中描述,并包含在新项目的 [First] 控制器中;
  • 第 5 行:调用 [View] 方法并传入一个参数。该参数即视图模型。视图本身未指定,因此将使用默认视图 [/Views/First/Action02]。视图模型 [ViewModel01] 被实例化,并使用来自操作模型 [ActionModel03] 的五项信息以及第 4 行构建的 [errors] 信息进行初始化。

现在我们编写视图 [/First/Action02.cshtml]:

其代码如下:


@model Exemple_03.Models.ViewModel01
@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action02</title>
</head>
<body>
  <h3>Informations du modèle de vue</h3>
  <ul>
    <li>Email : @Model.Email</li>
    <li>Jour : @Model.Jour</li>
    <li>Info1 : @Model.Info1</li>
    <li>Info2 : @Model.Info2</li>
    <li>Info3 : @Model.Info3</li>
    <li>Erreurs : @Model.Erreurs</li>
  </ul>
</body>
</html>

  • 新功能位于第 1 行。[@model] 标记用于设置视图模型的类型。随后,该模型通过 [@Model] 标记(第 16–21 行)进行引用;
  • 第 15–22 行:模型的信息以列表形式显示。

让我们来看几个执行 [Action02] 操作的示例。

首先,不带参数的情况:

Image

然后是带错误参数的情况:

Image

接着是带正确参数的情况:

Image

在此示例中,视图模型 [ViewModel01] 使用了动作模型 [ActionModel03] 中的信息。这种情况很常见。因此,我们可以使用一个既作为动作模型又作为视图模型的单一模型。我们创建一个新的模型 [ActionModel04]:

Image

其内容如下:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_03.Models
{
  [Bind(Exclude="Erreurs")]
  public class ActionModel04
  {
    // ---------------------- Action --------------------------------
    [Required(ErrorMessage = "Le paramètre email est requis")]
    [EmailAddress(ErrorMessage = "Le paramètre email n'a pas un format valide")]
    public string Email { get; set; }
 
    [Required(ErrorMessage = "Le paramètre jour est requis")]
    [RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramètre jour doit avoir 1 ou 2 chiffres")]
    public string Jour { get; set; }
 
    [Required(ErrorMessage = "Le paramètre info1 est requis")]
    [MaxLength(4, ErrorMessage = "Le paramètre info1 ne peut avoir plus de 4 caractères")]
    public string Info1 { get; set; }
 
    [Required(ErrorMessage = "Le paramètre info2 est requis")]
    [MinLength(2, ErrorMessage = "Le paramètre info2 ne peut avoir moins de 2 caractères")]
    public string Info2 { get; set; }
 
    [Required(ErrorMessage = "Le paramètre info3 est requis")]
    [MinLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    [MaxLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    public string Info3 { get; set; }
 
    // ---------------------- view --------------------------------
    public string Erreurs { get; set; }
  }
}

  • 第 8–28 行:包含完整性约束的动作模型。这些字段也将成为视图的一部分;
  • 第 31 行:视图模型特有的属性。它因第 5 行中的注解而被排除在操作模型之外。

我们创建以下新操作 [Action03]:


    // Action03
    public ViewResult Action03(ActionModel04 modèle)
    {
      modèle.Erreurs = getErrorMessagesFor(ModelState);
      return View(modèle);
}

  • 第 2 行:[Action03] 接收类型为 [ActionModel04] 的操作模型;
  • 第 5 行:并将该模型作为视图模型返回;
  • 第 4 行:并补充了 [Errors] 信息;

接下来只需创建视图 [/First/Action03.cshtml]:

  • 在 [1] 中:右键单击 [Action03] 代码,然后选择 [添加视图];
  • 在 [2] 中:输入默认视图名称;
  • 在 [3] 中:指定创建强类型视图;
  • 在 [4] 中:从下拉列表中选择正确的类,本例中为 [ActionModel04] 类;
  • 在 [5] 中:生成的视图。

我们将 [Action03] 视图的代码设置为与 [Action02] 视图相同。仅视图模板(第 1 行)和页面标题(第 11 行)有所不同:


@model Exemple_03.Models.ActionModel04
@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action03</title>
</head>
<body>
  <h3>Informations du modèle de vue</h3>
  <ul>
    <li>Email : @Model.Email</li>
    <li>Jour : @Model.Jour</li>
    <li>Info1 : @Model.Info1</li>
    <li>Info2 : @Model.Info2</li>
    <li>Info3 : @Model.Info3</li>
    <li>Erreurs : @Model.Erreurs</li>
  </ul>
</body>
</html>

现在让我们不带任何参数地调用 [Action03] 操作:

Image

结果与之前相同。通常会为操作和视图使用同一个模型,因为视图模型往往会复用操作模型中的信息。因此,我们使用一个更通用的模型,既可供操作使用,也可供其生成的视图使用。我们必须注意将不属于操作模型的信息从数据绑定中排除。否则,精明的用户可能会在我们不知情的情况下初始化视图模型的某些部分。

5.4. [Razor] – 入门

接下来我们将介绍 [Razor] 视图中的若干元素,主要是 foreach if 语句。

假设我们要将人员列表显示在 HTML 表格中。视图模型可以如下所示 [ViewModel02]:


namespace Exemple_03.Models
{
  public class ViewModel02
  {
    public Personne[] Personnes { get; set; }
    public ViewModel02()
    {
      Personnes = new Personne[] { new Personne { Nom = "Pierre", Age = 44 }, new Personne { Nom = "Pauline", Age = 12 } };
    }
  }
 
  public class Personne
  {
    public string Nom { get; set; }
    public int Age { get; set; }
  }
}
  • 视图模型是 [ViewModel02] 类,第 3–10 行;
  • 第 5 行:模型包含一个 [Person] 类型的人员数组,该数组在第 12–16 行中定义;
  • 第 6–10 行:模型的构造函数将第 5 行定义的 [People] 属性初始化为包含两个人的数组。

生成此模型作为输出的操作将是以下 [Action04]:


    // Action04
    public ViewResult Action04()
    {
      return View(new ViewModel02());
}
  • 第 2 行:该操作没有输入模型;
  • 第 4 行:它传递了其默认视图,即我们刚刚定义的 [ViewModel02] 模型的实例。

视图 [Action04.cshtml] 将显示模型 [ViewModel02]:

视图 [Action04.cshtml] 的代码如下:


@model Exemple_03.Models.ViewModel02
@using Exemple_03.Models
 
@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action04</title>
</head>
<body>
  <table border="1">
    <thead>
      <tr>
        <th>Nom</th>
        <th>Age</th>
      </tr>
    </thead>
    <tbody>
      @foreach (Personne p in Model.Personnes)
      {
        <tr>
          <td>@p.Nom</td>
          <td>@p.Age</td>
        </tr>
      }
    </tbody>
  </table>
</body>
</html>

  • 第 1 行:视图模板;
  • 第 2 行:导入第 24 行中使用的 [Person] 类的命名空间;
  • 第 16–32 行:显示模型中人员的 HTML 表格;
  • 第 24 行:C# 代码的开头由 @ 字符标记。foreach 语句将遍历模型中的所有人员;
  • 第 26–27 行:< 字符结束 C# 代码并开始 HTML 代码。随后再次出现 @ 字符,切换回 C# 并写入该人的姓名。接着再次出现 < 字符,切换回 HTML 模式;
  • 第 28 行:输出该人的年龄。

执行 [Action04] 操作将产生以下结果:

Image

视图中的其他元素可以通过集合来填充:列表(下拉列表或其他形式)、单选按钮和复选框。请看以下新示例,该示例展示了一个下拉列表。

模型 [ViewModel05] 将如下所示:


namespace Exemple_03.Models
{
  public class ViewModel05
  {
    public Personne2[] Personnes { get; set; }
    public int SelectedId { get; set; }
 
    public ViewModel05()
    {
      Personnes = new Personne2[] { 
        new Personne2 { Id = 1, Prénom = "Pierre", Nom = "Martino" }, 
        new Personne2 { Id = 2, Prénom = "Pauline", Nom = "Pereiro" }, 
        new Personne2 { Id = 3, Prénom = "Jacques", Nom = "Alfonso" } };
      SelectedId = 2;
    }
  }
 
  public class Personne2
  {
    public int Id { get; set; }
    public string Nom { get; set; }
    public string Prénom { get; set; }
  }
}

  • 第 18 行:一个包含三个属性的 [Person2] 类;
  • 第 3 行:用于视图的 [ViewModel05] 模型;
  • 第 5 行:要在下拉列表中显示的人员列表,格式为 [名字 姓氏];
  • 第 6 行:从下拉列表中选中的人员的 [Id];
  • 第 8–16 行:构造函数,用于创建一个包含三名人员的数组(第 10–13 行),并设置应显示为已选中的人员的 [Id]。

[Action05.cshtml] 视图将显示此模板:

其代码如下:


@model Exemple_03.Models.ViewModel05
@using Exemple_03.Models
 
@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action05</title>
</head>
<body>
  <select>
    @foreach (Personne2 p in Model.Personnes)
    {
      string selected = "";
      if (p.Id == Model.SelectedId)
      {
        selected = "selected=\"selected\"";
      }
      <option value="@p.Id" @selected>@p.Prénom @p.Nom</option>
    }
  </select>
</body>
</html>

HTML 下拉列表的特性已在第 2.5.2.6 节中介绍。让我们回顾一下:

Combo
<select size="1" name="cmbValues">
<option value="1">选项1</option>
<option selected="selected" value="2">选项2</option>
<option value="3">选项 3</option>
</select>

Image

HTML 标签
<select size=".." name="..">
<option [selected="selected"] value=”v”>...</option>
...
</select>
在列表中显示 <option>...</option> 标签之间的文本
属性
name="cmbValeurs":控件名称。
size="1":可见列表项的数量。size="1" 使列表相当于一个下拉框。
selected="selected":如果列表项包含此关键字,该项在列表中将显示为已选中。在上例中,列表项 choice2 在组合框首次显示时即作为选中项出现。
value=”v”:如果用户选择了该项目,则该值 [v] 将被提交到服务器。如果此属性不存在,则显示和选中的文本将被提交到服务器。

第 17–25 行的代码生成 <option> 标签,这些标签被放置在第 16 行的 <select> 标签内。

  • 第 17 行:遍历模型中的人员列表;
  • 第 20 行:检查当前人员是否为待选人员。如果是,则准备文本 selected="selected" 以插入到 <option> 标签中;
  • 第 24 行:写入 <option> 标签。

我们将此操作命名为 [Action05]:

  • 在 [1,2] 中,人员以 [名字 姓氏] 的格式显示;
  • 在 [1,2] 中,选中的人是 [Id] 等于 2 的那个。

现在让我们查看上述页面的 HTML 源代码:


<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action05</title>
</head>
<body>
  <select>
      <option value="1" >Pierre Martino</option>
      <option value="2" selected=&quot;selected&quot;>Pauline Pereiro</option>
      <option value="3" >Jacques Alfonso</option>
  </select>
</body>
</html>

  • 第 10–12 行:由 [Razor] 代码生成的三个 <option> 标签;
  • 第 11 行:[Id]=2 的用户确实已被选中。

以上两个示例已足以说明问题。在编写 [Razor] 视图时,必须抵制将逻辑放入其中的诱惑。虽然 C# 代码允许我们这样做,但在 MVC 模型中,逻辑必须位于操作(Action)或更底层([业务逻辑、DAO]),而不能出现在视图中。 即使遵循 MVC 模式,你仍可能因计算中间值而在视图中包含大量逻辑。这可能意味着所使用的模型不够详细。模型必须包含视图所需的最终值,这样视图就无需自行计算。一个好的视图应具有最少的逻辑和清晰的 HTML 结构。如果插入过多的 C# 代码,HTML 结构可能会变得难以阅读。

在上例中,用户可以使用下拉列表,而我们需要知道用户选择了哪个人。为此,我们需要一个表单。

5.5. 表单 – 入门

呈现给用户的表单如下所示:

Image

视图模型将使用之前用过的 [ViewModel05] 模型。用于显示此视图的操作如下:


    // Action06-GET
    [HttpGet]
    public ViewResult Action06()
    {
      return View("Action06Get",new ViewModel05());
}

  • 第 2 行:该操作只能通过 HTTP GET 请求调用;
  • 第 5 行:将使用类型为 [ViewModel05] 的实例作为模型来显示视图 [/First/Action06Get.cshtml]。

视图 [/First/Action06Get.cshtml] 将如下所示:


@model Exemple_03.Models.ViewModel05
@using Exemple_03.Models
 
@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action06-GET</title>
</head>
<body>
  <h3>Action06 - GET</h3>
  <p>Choisissez une personne</p>
  <form method="post" action="/First/Action06">
    <select name="personneId">
      @foreach (Personne2 p in Model.Personnes)
      {
        string selected = "";
        if (p.Id == Model.SelectedId)
        {
          selected = "selected=\"selected\"";
        }
        <option value="@p.Id" @selected>@p.Prénom @p.Nom</option>
      }
    </select>
    <input name="valider" type="submit" value="Valider" />
  </form>
</body>
</html>

主要新功能如下:

  • 第18行:为了让浏览器传输用户输入的信息,我们需要一个表单。该表单由第18行和第31行的<form>标签界定。

HTML <form> 标签已在第 2.5.2.1 节中介绍过。让我们回顾一下它的特性:

表单

<form method="post" action="FormulairePost.aspx">
HTML 标签
<form name="..." method="..." action="...">...</form>
属性
name="frmexample":表单名称 - 可选
method="...":浏览器用于将表单中收集的值发送至 Web 服务器的方法
action="...":表单中收集的值将被发送到的 URL。
Web表单由<form>...</form>标签包围。表单可以有一个名称(name="xx")。这适用于表单中所有 控件。 表单的目的是收集用户通过键盘或鼠标输入的信息,并将其发送至某个 Web 服务器 URL。具体是哪个?即 action="URL" 属性中指定的那个。如果缺少此属性,信息将发送至包含该表单的文档的 URL。 Web客户端可以使用两种不同的方法(即POST和GET)向Web服务器发送数据。<form>标签中的method="method"属性(其中method设置为GET或POST)会告知浏览器,应使用哪种方法将表单中收集的信息发送至action="URL"属性指定的URL。若未指定method属性,则默认使用GET方法。
  • 第 18 行:我们可以看到,表单值将通过 HTTP POST 请求发送至 URL [/First/Action06];
  • 第 30 行:表单必须包含一个 [submit] 按钮。该按钮会触发将输入的值提交至 <form> 标签中 [action] 属性指定的 URL。

当用户点击 [Submit] 按钮时,浏览器究竟会发送什么?这一点已在第 2.5.3.1 节中解释过。让我们回顾一下相关内容:


HTML 控件


视觉


返回值

<input type="radio" value="Yes" name="R1"/>是
<input type="radio" name="R1" value="No" checked="checked"/>No
R1=是
- 用户所选单选按钮的 value 属性的值。
<input type="checkbox" name="C1" value="one"/>1
<input type="checkbox" name="C2" value="two" checked="checked"/>2
<input type="checkbox" name="C3" value="three"/>3
C1=one
C2=two
- 用户选中的复选框的 value 属性值
<input type="text" name="txtInput" size="20" value="a few words"/>
txtInput=Web+编程
- 用户在输入框中输入的文本。空格已被替换为 + 号
<input type="password" name="txtMdp" size="20" value="aPassword"/>
txtPassword=thisIsSecret
- 用户在输入框中输入的文本
<textarea rows="2" name="inputArea" cols="20">
line1
行2
第3行
</textarea>
输入字段=Web基础知识%0D%0A
Web+编程
- 用户在输入字段中输入的文本。%OD%OA 是换行标记。空格已被 + 号替换
<select size="1" name="cmbValues">
<option value='1'>选项1</option>
<option selected="selected" value='2'>选项2</option>
<option value='3'>选项3</option>
</select>
cmbValues=3
- 用户所选元素的 [value] 属性
<select size="3" name="lst1">
<option selected="selected" value='1'>list1</option>
<option value='2'>list2</option>
<option value='3'>列表3</option>
<option value='4'>列表4</option>
<option value='5'>列表5</option>
</select>
lst1=3
- 用户所选元素的 [value] 属性
<select size="3" name="lst2" multiple="multiple">
<option selected="selected" value='1'>list1</option>
<option value='2'>list2</option>
<option selected="selected" value='3'>list3</option>
<option value='4'>list4</option>
<option value='5'>列表5</option>
</select>
lst2=1
lst2=3
- 用户所选元素的 [value] 属性
<input type="submit" value="提交" name="cmdSubmit"/>
 
cmdRenvoyer=提交
- 用于将表单数据发送至服务器的按钮的 name 和 value 属性
<input type="hidden" name="secret" value="aValue"/>
 
secret=aValue
- 隐藏字段的 value 属性

在我们的表单中,有两个可以发送值的标签:


    <select name="personneId">
...
</select>

以及


<input name="valider" type="submit" value="Valider" />

如果用户选择第2个人,数据将以以下格式提交:

personneId=2&valider=Valider

参数名称即参与POST请求的标签的[name]属性。若缺少该属性,标签将不会发送任何值。因此,在上例中,我们可以省略[submit]按钮的name="valider"属性。发送的值是按钮的[value]属性。在此,该信息与我们无关。 有时表单包含多个 [submit] 按钮。在这种情况下,确定哪个按钮被点击至关重要。因此,我们将为不同的按钮分配 [name] 属性。

<select> 标签由一系列 <option> 标签组成:


    <select name="personneId">
        <option value="1" >Pierre Martino</option>
        <option value="2" selected=&quot;selected&quot;>Pauline Pereiro</option>
        <option value="3" >Jacques Alfonso</option>
</select>

将已选选项的 [value] 属性的值提交。如果缺少此属性,则提交该选项显示的文本——例如 [Pierre Martino]。

字符串

personneId=2&valider=Valider

将被发送到以下 URL [/First/Action06]:


    // Action06-POST
    [HttpPost]
    public ViewResult Action06(ActionModel06 modèle)
    {
      return View("Action06Post",modèle);
}

您可能还记得,我们之前已经有一个名为 [Action06] 的操作:


    // Action06-GET
    [HttpGet]
    public ViewResult Action06()
    {
      return View("Action06Get",new ViewModel05());
}

只要两个操作不处理相同的 HTTP 请求,就可以存在两个同名的操作:

  • 第 3 行中的 [Action06] 处理 POST 请求(第 2 行);
  • 第 c 行的 [Action06] 处理 GET 请求(第 b 行)。

处理 POST 请求的 [Action06] 操作将接收以下参数字符串:

personneId=2&valider=Valider

我们需要一个操作模型来封装这些值。这将是一个名为 [ActionModel06] 的模型:


using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
  public class ActionModel06
  {
    [Required(ErrorMessage = "Le paramètre [personneId] est requis")]
    public int PersonneId { get; set; }
 
    [Required(ErrorMessage = "Le paramètre [valider] est requis")]
    public string Valider { get; set; }
  }
}

[Action06] 操作接收此模型,并将其原样传递给后续的 [Action06Post] 视图(操作的第 5 行):


@model Exemple_03.Models.ActionModel06
 
@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action06Post</title>
</head>
<body>
  <h3>Action06 - POST</h3>
  Valeurs postées :
  <ul>
    <li>ID de la personne sélectionnée : @Model.PersonneId</li>
    <li>Commande utilisée : @Model.Valider</li>
  </ul>
</body>
</html>

该模型显示在第 18 行和第 19 行。

让我们来看一个示例:

在 [1] 中,我们选择 [Id] 等于 3 的第三个人。在 [2] 中,我们提交表单。在 [3] 中,显示接收到的值。在 [4,5] 中,我们可以看到调用了同一个 URL,一个通过 GET 请求 [4],另一个通过 POST 请求 [5]。这一点在 URL 中是无法看出来的。

在POST请求后显示的视图中,我们可能希望显示所选人员的姓名而非其编号。因此,我们必须更新POST视图及其模型。

我们创建一个操作 [Action07] 来处理这种情况。该操作需要使用用户的会话来存储人员列表。我们将遵循第 4.10 节中讨论的模式,该模式允许我们在操作的模型中包含来自 [Application] 和 [Session] 作用域的数据。

会话模型将采用以下 [SessionModel] 类:


namespace Exemple_03.Models
{
  public class SessionModel
  {
    public Personne2[] Personnes { get; set; }
  }
}
  • 第 2 行:会话将存储下拉列表中显示的人员列表;

我们需要将前面的类型 [SessionModel] 绑定到一个我们将命名为 [SessionModelBinder] 的绑定器上。这与第74页描述的绑定器相同:

Image


using System.Web.Mvc;
 
namespace Exemple_03.Infrastructure
{
  public class SessionModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      // render scope data [Session]
      return controllerContext.HttpContext.Session["data"];
    }
  }
}

[SessionModel] 模型与其 [SessionModelBinder] 之间的绑定在 [Global.asax] 中定义:


public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
      ...
 
      // model binders
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
    }
    // Session
    public void Session_Start()
    {
      Session["data"] = new SessionModel();
    }
  }
  • 第 8 行:模型在 [Application_Start] 中与其绑定器进行了绑定;
  • 第 13 行:将一个 [SessionModel] 类型的实例添加到与键 [data] 关联的会话中。

完成上述操作后,[Action07] 操作如下:


    // Action07-GET
    [HttpGet]
    public ViewResult Action07(SessionModel session)
    {
      ViewModel05 modèleVue = new ViewModel05();
      session.Personnes= modèleVue.Personnes;
      return View("Action07Get", modèleVue);
}
  • 第 3 行:该操作检索 [SessionModel] 类型,即与 [data] 键关联的 [Session] 作用域数据;
  • 第 5 行:我们创建视图模型;
  • 第 6 行:将 people 数组放入会话中。我们在接下来的 POST 请求中会用到它。HTTP 协议是一种无状态协议。必须使用会话来在请求之间维护状态。会话是特定于用户的,并由 Web 服务器管理;
  • 第 7 行:显示视图 [Action07Get.cshtml]。内容如下:

@model Exemple_03.Models.ViewModel05
@using Exemple_03.Models
...
<body>
  <h3>Action07 - GET</h3>
  <p>Choisissez une personne</p>
  <form method="post" action="/First/Action07">
....
  </form>
</body>
</html>

它与我们之前分析过的 [Action06Get.cshtml] 视图完全相同。主要区别在于第 7 行:表单值将被提交到的 URL。这些数据将由后续的 [Action07] 操作进行处理:


    // Action07-POST
    [HttpPost]
    public ViewResult Action07(SessionModel session, ActionModel06 modèle)
    {
      Personne2 personne = session.Personnes.Where(p => p.Id == modèle.PersonneId).First<Personne2>();
      string strPersonne = string.Format("{0} {1}", personne.Prénom, personne.Nom);
      return View("Action07Post", (object)strPersonne);
}
  • 第 3 行:提交的值被封装在之前(下文)已使用的操作模型 [ActionModel06] 中:

using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
  public class ActionModel06
  {
    [Required(ErrorMessage = "Le paramètre [personneId] est requis")]
    public int PersonneId { get; set; }
 
    [Required(ErrorMessage = "Le paramètre [valider] est requis")]
    public string Valider { get; set; }
  }
}

  • 第 3 行:第一个参数是与 [data] 键关联的 [Session] 作用域数据;
  • 第 5 行:一个 LINQ 查询检索具有已提交 [Id] 的用户;
  • 第 6 行:我们构建将由 [Action07Post] 视图(第 8 行)显示的字符串;
  • 第 7 行:为了调用正确的 [View] 构造函数,必须将 [string] 类型强制转换为 [object]。

[Action07Post.cshtml] 视图如下:


@model string
 
@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action07-Post</title>
</head>
<body>
  <h3>Action07-POST</h3>
  Vous avez sélectionné [@Model].
</body>
</html>

  • 第 1 行:模型类型为 [string];
  • 第 16 行:显示该字符串。

以下是一个执行示例:

Image

Image

5.6. 表单——一个完整的示例

在第 2.5.2.1 节中,我们分析了以下 HTML 表单:

Image

我们将研究一个用于显示(GET)此表单的操作 [Action08Get],以及一个用于处理(POST)用户输入值的操作 [Action08Post]。这是一个经典的配置。

上文中的视图模型 [1] 将是 [ViewModel08] 类的实例。该类将同时充当:

  • [Action08Get] 操作中 GET 请求生成的视图模型;
  • [Action08Post] 操作在处理 POST 请求时的模型。

5.6.1. [Application] 作用域模型

我们将假设单选按钮、复选框和各种列表所显示的元素属于 [Application] 作用域数据。这是一种常见的情景。这些信息来源于配置文件,或在应用程序启动时通过 [Global.asax] 中的 [Application_Start] 方法访问的数据库。该方法的实现如下:


    protected void Application_Start()
    {
....
 
      // model binders
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
      ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());
 
      // scope data [Application]
      Application["data"] = new ApplicationModel();
}

  • 第 7 行:[ApplicationModel] 类型(稍后将进行说明)与我们在第 74 页已介绍过的 [ApplicationModelBinder] 数据绑定器相关联;
  • 第 10 行:一个 [ApplicationModel] 类型的实例被存储在应用程序字典中,并关联到键 [data]。

[ApplicationModel] 类用于封装 [Application] 作用域内的所有数据。在此,它将封装表单需要显示的数据:


namespace Exemple_03.Models
{
  public class ApplicationModel
  {
    // the collections to be displayed in the form
    public Item[] RadioButtonFieldItems { get; set; }
    public Item[] CheckBoxesFieldItems { get; set; }
    public Item[] DropDownListFieldItems { get; set; }
    public Item[] SimpleChoiceListFieldItems { get; set; }
    public Item[] MultipleChoiceListFieldItems { get; set; }
 
    // initializing fields and collections
    public ApplicationModel()
    {
      RadioButtonFieldItems = new Item[]{
        new Item {Value="1",Label="oui"},
        new Item {Value="2", Label="non"}
      };
      CheckBoxesFieldItems = new Item[]{
        new Item {Value="1",Label="1"},
        new Item {Value="2", Label="2"},
        new Item {Value="3", Label="3"}
      };
      DropDownListFieldItems = new Item[]{
        new Item {Value="1",Label="choix1"},
        new Item {Value="2", Label="choix2"},
        new Item {Value="3", Label="choix3"}
      };
      SimpleChoiceListFieldItems = new Item[]{
        new Item {Value="1",Label="liste1"},
        new Item {Value="2", Label="liste2"},
        new Item {Value="3", Label="liste3"},
        new Item {Value="4", Label="liste4"},
        new Item {Value="5", Label="liste5"}
      };
      MultipleChoiceListFieldItems = new Item[]{
        new Item {Value="1",Label="liste1"},
        new Item {Value="2", Label="liste2"},
        new Item {Value="3", Label="liste3"},
        new Item {Value="4", Label="liste4"},
        new Item {Value="5", Label="liste5"}
      };
    }
    // the collections item
    public class Item
    {
      public string Label { get; set; }
      public string Value { get; set; }
    }
 
  }
}

  • 第 45–49 行:代表各种集合的表单控件。[Label] 是表单控件显示的文本,[Value] 是该控件被选中时提交的值;
  • 第 6 行:单选按钮显示的集合;
  • 第 7 行:复选框显示的集合;
  • 第 8 行:下拉列表显示的集合;
  • 第 9 行:单选列表显示的集合;
  • 第 10 行:多选列表显示的集合;
  • 第 13–43 行:这些集合由类的无参构造函数初始化。

这些集合将填充以下表单:

5.6.2. [Action08Get] 操作的模型

前一个表单将通过以下 [Action08Get] 操作显示:


    // Action08-GET
    [HttpGet]
    public ViewResult Action08Get(ApplicationModel application)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return View("Formulaire", new ViewModel08(application));
}
  • 第 2 行:[Action08Get] 仅响应 [GET] 请求;
  • 第 3 行:它接收我们刚才描述的应用程序模型作为参数;
  • 第 5 行:它在动态容器 [ViewBag] 中初始化数据;
  • 第 6 行:使用 [ViewModel08] 模型渲染视图 [/First/Formulaire.cshtml]。该模型即前文介绍的表单模型。为此,我们将定义待显示元素的应用程序模型传递给构造函数。

5.6.3. [Form] 视图模型

[ViewModel08] 类将作为表单的模型。该类定义如下:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;
 
namespace Exemple_03.Models
{
  public class ViewModel08
  {
    // input fields
    public string RadioButtonField { get; set; }
    public string[] CheckBoxesField { get; set; }
    public string TextField { get; set; }
    public string PasswordField { get; set; }
    public string TextAreaField { get; set; }
    public string DropDownListField { get; set; }
    public string SimpleChoiceListField { get; set; }
    public string[] MultipleChoiceListField { get; set; }
 
    // the collections to be displayed in the form
    public ApplicationModel.Item[] RadioButtonFieldItems { get; set; }
    public ApplicationModel.Item[] CheckBoxesFieldItems { get; set; }
    public ApplicationModel.Item[] DropDownListFieldItems { get; set; }
    public ApplicationModel.Item[] SimpleChoiceListFieldItems { get; set; }
    public ApplicationModel.Item[] MultipleChoiceListFieldItems { get; set; }
 
    // manufacturers
    public ViewModel08()
    {
    }
 
    public ViewModel08(ApplicationModel application)
    {
      // initialization collections
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // field initialization
      RadioButtonField = "2";
      CheckBoxesField = new string[] { "2" };
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
      MultipleChoiceListField = new string[] { "1", "3" };
    }
  }
}

  • 在表单中,元素分为两类:显示元素和输入元素;
  • 第 20–24 行定义了要显示的元素。这些是表单的各种集合。它们位于应用程序模型中(第 34–38 行);
  • 第 10–17 行:定义表单的输入字段;
  • 第 10 行:[RadioButtonField] 将获取表单后续行提交的


        <!-- radio buttons -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
<input type="radio" name="RadioButtonField" value="1" />oui              
<input type="radio" name="RadioButtonField" value="2" checked=&quot;checked&quot;/>non              
          </td>
</tr>

请注意第 5 行和第 6 行中,这两个单选按钮的 [name] 属性即为将被初始化的属性名称。在提交的数据中,你会发现一个形式如下所示的字符串:


param1=val1&RadioButtonField=2&param2=val2

(如果用户选中了标有 [no] 的选项)。实际上,被提交的是被选中选项的 [value] 属性。

  • 第 11 行:[CheckBoxesField] 将检索由表单中以下行提交的值


        <!-- checkboxes -->
        <tr>
          <td>Cases à cocher</td>
          <td>
<input type="checkbox" name="CheckBoxesField" value="1" />1              
<input type="checkbox" name="CheckBoxesField" value="2" checked=&quot;checked&quot;/>2              
<input type="checkbox" name="CheckBoxesField" value="3" />3              
</td>

请注意第 5 行和第 6 行中,复选框的 [name] 属性即为将被初始化的属性名称。在提交的数据中,你会发现一个符合以下格式的字符串:


param1=val1&CheckBoxesField=2&CheckBoxesField=3&param2=val2

(若用户勾选了标记为 [2] 和 [3] 的复选框)。 提交的是已选中选项的 [value] 属性。由于可能提交多个同名参数,[CheckBoxesField] 是一个值数组,而非单一值。如果未选中任何复选框,[CheckBoxesField] 参数将不会出现在提交的字符串中,同名的模型属性也不会被初始化。正如我们将看到的,这可能会带来问题。

  • 第 12 行:[TextField] 将获取表单后续行提交


          <!-- single-line text input field -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input type="text" name="TextField" value="quelques mots" size="30" />
            </td>
</tr>

第 5 行:输入字段的 [name] 属性即为将被初始化的属性名称。在提交的数据中,你会发现一个符合以下格式的字符串:


param1=val1&TextField=abcdef&param2=val2

(假设用户在输入字段中输入了 [abcdef])。

  • 第 13 行:[PasswordField] 将检索表单中后续几行提交的


        <!-- password entry field -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="PasswordField" value="secret" size="30" />
          </td>
</tr>

第 5 行:输入字段的 [name] 属性是待初始化的属性的名称。在提交的数据中,你会发现一个符合以下格式的字符串:


param1=val1&PasswordField=abcdef&param2=val2

(假设用户在输入字段中输入了 [abcdef])。

  • 第 14 行:[TextAreaField] 将检索表单中后续行提交的值:


        <!-- le champ de saisie texte multilignes -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea name="TextAreaField" cols="40" rows="3">ligne1
ligne2</textarea>
          </td>
</tr>

第 5 行:输入字段的 [name] 属性即为将被初始化的属性名称。在提交的数据中,您将看到以下格式的字符串:


param1=val1&TextAreaField=abcdef%0D%OAhijk&param2=val2

如果用户在输入字段中输入了 [abcdef],随后是一个换行符,然后是 [ijk]。

  • 第 15 行:[DropDownListField] 将检索表单后续行提交


        <!-- the drop-down list -->
        <tr>
          <td>Liste déroulante</td>
          <td>
            <select name="DropDownListField">
<option value="1" >choix1</option>
<option value="2" selected=&quot;selected&quot;>choix2</option>
<option value="3" >choix3</option>
            </select>
</tr>

第 5 行:<select> 标签的 [name] 属性是待初始化的属性名称。在提交的数据中,你会发现一个符合以下格式的字符串:


param1=val1&DropDownListField=1&param2=val2

(若用户选择了 [choice1] 选项)。提交的是所选选项的 [value] 属性。

  • 第 16 行:[SingleChoiceListField] 将检索表单中后续几行提交


        <!-- single-choice list -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
            <select name="SimpleChoiceListField" size="3">
<option value="1" >liste1</option>
<option value="2" >liste2</option>
<option value="3" selected=&quot;selected&quot;>liste3</option>
<option value="4" >liste4</option>
<option value="5" >liste5</option>
 
            </select>
</tr>

第 5 行:<select> 标签的 [name] 属性是待初始化的属性名称。[size="3"] 属性确保不会显示下拉列表。在提交的数据中,我们将找到以下格式的字符串:


param1=val1&SimpleChoiceListField=3&param2=val2

(前提是用户选择了 [liste3] 选项)。提交的是所选选项的 [value] 属性。如果未选择任何项目,则提交的字符串中可能不包含 [SingleChoiceListField] 参数。

  • 第 17 行:[MultipleChoiceListField] 将检索表单中后续行提交


        <!-- multiple choice list -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
            <select name="MultipleChoiceListField" size="3" multiple="multiple">
<option value="1" selected=&quot;selected&quot;>liste1</option>
<option value="2" >liste2</option>
<option value="3" selected=&quot;selected&quot;>liste3</option>
<option value="4" >liste4</option>
<option value="5" >liste5</option>
            </select>
</tr>

第 5 行:<select> 标签的 [name] 属性是待初始化的属性名称。[size="3"] 属性确保不显示下拉列表,而 [multiple] 属性允许用户按住 [Ctrl] 键选择多个项目。在提交的数据中,你会发现一个符合以下格式的字符串:


param1=val1&MultipleChoiceListField=1&MultipleChoiceListField=3&param2=val2

(假设用户选择了选项 [list1] 和 [list3])。 提交的是所选选项的 [value] 属性。由于可能提交多个同名参数,[MultipleChoiceListField] 是一个值数组,而非单一值。如果未选中任何复选框,[MultipleChoiceListField] 参数将不会出现在提交的字符串中,且同名的模型属性也不会被初始化。

前面展示的各种输入字段将接收表单提交的值。它们也可以在提交表单之前进行初始化。这里就是这样做的:


      // initialisation champs
      RadioButtonField = "2";
      CheckBoxesField = new string[] { "2" };
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
MultipleChoiceListField = new string[] { "1", "3" };

如果这些值是在表单发送 POST 请求后获取的,这意味着用户已:

  • 第 2 行:从单选按钮中选择了 [no] 选项;
  • 第 3 行:在复选框中选中了 [2] 选项;
  • 第 4 行:在输入框中输入了 [a few words];
  • 第 5 行:将 [secret] 作为密码输入;
  • 第 6 行:在多行输入框中输入 [line1\nline2];
  • 第 7 行:从下拉列表中选择了 [option2] 选项;
  • 第 8 行:从单选列表中选择了选项 [list3];
  • 第 9 行:从多选列表中选择了选项 [list1] 和 [list3];

我们将假设已发出 POST 请求,并希望将表单原样返回。这正是当向用户返回错误表单时发生的情况。表单将完全按照用户输入的原样返回。

5.6.4. [Form] 视图

[/First/Formulaire.cshtml] 视图用于显示表单:


@model Exemple_03.Models.ViewModel08
@using Exemple_03.Models
@{
  Layout = null;
}
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Formulaire</title>
</head>
<body>
  <form method="post" action="Action08Post">
    <h2>Formulaire ASP.NET MVC</h2>
    <h3>Affiché par : @ViewBag.info</h3>
    <table>
      <thead></thead>
      <tbody>
        <!-- radio buttons -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
            @foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
            {
              string strChecked = item.Value == @Model.RadioButtonField ? "checked=\"checked\"" : "";
              <input type="radio" name="RadioButtonField" value="@item.Value" @strChecked/>@item.Label
              <text/>
            }
          </td>
        </tr>
...
      </tbody>
    </table>
    <input type="submit" value="Valider" />
  </form>
</body>
</html>
  • 第 1 行:[ViewModel08] 是表单模型;
  • 第 12 行:表单的 <form> 标签。该表单将使用 [POST] 方法(method 属性)提交至 URL [/First/Action08Post](action 属性);
  • 第 33 行:用于提交表单的 [submit] 按钮;
  • 第 22–27 行:显示单选按钮:

Image

  • 第 22 行:遍历由单选按钮显示的集合;
  • 第 24 行:其 [value] 属性与 [RadioButtonField] 属性值对应的按钮必须被选中。为此,该按钮必须具有 [checked="checked"] 属性;
  • 第 25 行:生成 <input type="radio"> 标签,其值为 [@item.Value],标签为 [@item.Label];
  • 第 26 行:<text/> 标签不是被识别的 HTML 标签。它仅用于 [Razor]。当遇到该标签时,[Razor] 会生成一个换行符。这不会影响显示的表单,但会影响生成的 HTML 代码。 因此,<input type="radio"> 标签被分在两行,而非同一行。当您在浏览器中查看页面源代码时,这会使代码更易于阅读;

让我们回顾一下视图中的其他元素:


        <!-- checkboxes -->
        <tr>
          <td>Cases à cocher</td>
          <td>
            @{
              foreach (ApplicationModel.Item item in @Model.CheckBoxesFieldItems)
              {
                string strChecked = @Model.CheckBoxesField.Contains(item.Value) ? "checked=\"checked\"" : "";
              <input type="checkbox" name="CheckBoxesField" value="@item.Value" @strChecked/>@item.Label
              <text/>
              }
            }
</td>
  • 第 6 行:我们遍历由复选框显示的集合;
  • 第 8 行:[value] 属性值为 [CheckBoxesField] 属性所含值之一的复选框必须被选中。为此,它必须具有 [checked="checked"] 属性。我们使用 LINQ 表达式来判断某个值是否包含在数组中;
  • 第 25 行:生成 <input type="checkbox"> 标签,其值为 [@item.Value],标签为 [@item.Label];

<!-- single-line text input field -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input type="text" name="TextField" value="@Model.TextField" size="30" />
            </td>
          </tr>
        <!-- password entry field -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="PasswordField" value="@Model.PasswordField" size="30" />
          </td>
        </tr>
        <!-- multiline text input field -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea name="TextAreaField" cols="40" rows="3">@Model.TextAreaField</textarea>
          </td>
        </tr>
  • 第 5、12 行:我们将模型的值赋给标签的 [value] 属性;
  • 第 19 行:与上文相同,但语法不同。

        <!-- the drop-down list -->
        <tr>
          <td>Liste déroulante</td>
          <td>
            <select name="DropDownListField">
              @{
                foreach (ApplicationModel.Item item in @Model.DropDownListFieldItems)
                {
                  string strChecked = item.Value == @Model.DropDownListField ? "selected=\"selected\"" : "";
                <option value="@item.Value" @strChecked>@item.Label</option>
                }
              }
            </select>
</tr>
  • 第 7 行:遍历下拉列表显示的集合;
  • 第 9 行:随后必须选中一个 [value] 属性与 [DropDownListField] 属性值匹配的选项。为此,该选项必须具有 [selected="selected"] 属性;
  • 第 25 行:生成 <option value="value">label</option> 标签,其中 value 为 [@item.Value],label 为 [@item.Label];

        <!-- single-choice list -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
            <select name="SimpleChoiceListField" size="3">
              @{
                foreach (ApplicationModel.Item item in @Model.SimpleChoiceListFieldItems)
                {
                  string strChecked = item.Value == @Model.SimpleChoiceListField ? "selected=\"selected\"" : "";
                <option value="@item.Value" @strChecked>@item.Label</option>
                }
              }
            </select>
</tr>

说明与下拉列表相同。


        <!-- multiple choice list -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
            <select name="MultipleChoiceListField" size="3" multiple="multiple">
              @{
                foreach (ApplicationModel.Item item in @Model.MultipleChoiceListFieldItems)
                {
                  string strChecked = @Model.MultipleChoiceListField.Contains(item.Value) ? "selected=\"selected\"" : "";
                <option value="@item.Value" @strChecked>@item.Label</option>
                }
              }
            </select>
</tr>
  • 第 7 行:我们遍历列表显示的集合;
  • 第 9 行:必须选择一个 [value] 属性值为 [MultipleChoiceListField] 属性所含值之一的选项。为此,该选项必须具有 [selected="selected"] 属性。我们使用 LINQ 表达式来判断某个值是否包含在数组中;
  • 第 10 行:生成 <option value="value">label</option> 标签,其中 [@item.Value] 作为值,[@item.Label] 作为标签;

5.6.5. 处理表单 POST

我们看到表单将提交至 [Action08Post] 操作:


  <form method="post" action="Action08Post">

[Action08Post] 操作如下:


    // Action08-POST
    [HttpPost]
    public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      ViewModel08 modèle = new ViewModel08(application);
      TryUpdateModel(modèle,posted);
      return View("Formulaire", modèle);
}
  • 第 3 行:应用程序模型作为参数传递,同时还包含提交的值。这些值以 [FormCollection] 类型提供。通过表达式 posted["RadioButtonField"] 获取提交的 [RadioButtonField] 参数的值。这将返回一个字符串或空指针。如果你写成 posted["CheckBoxesField"],则会得到一个字符串数组或空指针;
  • 那么为什么不这样写:

public ViewResult Action08Post(ApplicationModel application, ViewModel08 posted)

原因有二:

  • 首先,框架会使用无参构造函数来实例化 [ViewModel08] 模型,这将导致模型的集合未被初始化;
  • 其次,我们希望控制模型的初始化来源。我们知道模型有四种可能的来源:GET 或 POST 请求的参数、所用的路由,以及上传文件中的数据。在此,我们仅希望使用提交的值来初始化模型。
  • 第 6 行:我们使用正确的构造函数实例化模型;
  • 第 7 行:使用表单提交的值对其进行初始化。此操作完成后,模型将与用户的输入内容相匹配;
  • 第 8 行:我们再次显示表单。用户将看到与提交时完全一致的表单。

让我们看一个示例:

在[2]中,[POST]的结果准确反映了在[1]中输入的内容。

5.6.6. 处理 POST 错误

我们提到,如果未对 [CheckBoxesField、SimpleChoiceListField、MultipleChoiceListField] 字段进行选中或选择,则相应的参数不会包含在提交的字符串中,因此同名的模型属性也不会被初始化。

让我们来看以下示例:

  • 在 [1] 中,未勾选任何复选框;
  • 在 [2] 中,[POST] 请求返回了一个被选中的复选框。

解释如下:

  • 由于未选中任何复选框,[CheckBoxesField] 参数未包含在提交的值中;
  • [Action08Post] 操作的执行流程如下:

    [HttpPost]
    public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = ...
      ViewModel08 modèle = new ViewModel08(application);
      TryUpdateModel(modèle,posted);
      return View("Formulaire", modèle);
}
  • 第 5 行:表单模型被实例化。但是,所使用的构造函数将数组 ["2"] 赋值给 [CheckBoxesField] 属性;
  • 第 6 行:将提交的值保存到模型中。由于 [CheckBoxesField] 参数不属于提交的值,因此同名的属性未被赋值。它因此保留了 ["2"] 的值,这意味着当表单显示时,第 2 个复选框会被选中,而实际上它不应被选中。

此问题可通过多种方式解决。我们选择在 [Action08Post] 操作的代码中进行处理:


// Action08-POST
    [HttpPost]
    public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      ViewModel08 modèle = new ViewModel08(application);
      TryUpdateModel(modèle,posted);
      // processing of non-posted values
      if (posted["CheckBoxesField"] == null)
      {
        modèle.CheckBoxesField = new string[] { };
      }
      if (posted["SimpleChoiceListField"] == null)
      {
        modèle.SimpleChoiceListField = "";
      }
      if (posted["MultipleChoiceListField"] == null)
      {
        modèle.MultipleChoiceListField = new string[] { };
      }
      // form display
      return View("Formulaire", modèle);
    }
  • 第 9–20 行:我们检查某些参数是否已被提交。如果未提交,则使用用户未输入任何内容的默认值对其进行初始化。下拉列表未进行此检查,因为与其他列表不同,它始终有一个选项被选中。

欢迎读者测试此新版本。

5.7. 使用专门用于表单生成的方法

5.7.1. 新表单

我们创建了一个新表单 [Form2.cshtml],它将生成一个与之前完全相同的表单:

让我们重新审视用于生成表单下拉列表的代码:


        <!-- the drop-down list -->
        <tr>
          <td>Liste déroulante</td>
          <td>
            <select name="DropDownListField">
              @{
                foreach (ApplicationModel.Item item in @Model.DropDownListFieldItems)
                {
                  string strChecked = item.Value == @Model.DropDownListField ? "selected=\"selected\"" : "";
                <option value="@item.Value" @strChecked>@item.Label</option>
                }
              }
            </select>
</tr>

这段代码有两个缺点:

  • 最显著的是,由于代码过于复杂,导致组件的本质(本例中为下拉列表)无法体现;
  • 第 5 行:若在作为 [name] 属性的模型属性名称上出现错误,您将直到运行时才会察觉。

ASP.NET MVC 提供了一组名为 [HTML Helpers] 的专用方法,顾名思义,这些方法旨在简化 HTML 的生成,特别是表单的生成。利用这些类,上文提到的下拉列表 可以改写为如下形式:


        <!-- the drop-down list -->
        <tr>
          <td>Liste déroulante</td>
          <td>@Html.DropDownListFor(m => m.DropDownListField,
           new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
          </td>
</tr>

下拉列表由第 4–5 行代码生成。该代码的复杂度显著降低。为下拉列表生成的 HTML 代码如下:


        <!-- the drop-down list -->
        <tr>
          <td>Liste déroulante</td>
          <td><select id="DropDownListField" name="DropDownListField"><option value="1">choix1</option>
<option selected="selected" value="2">choix2</option>
<option value="3">choix3</option>
</select></td>
</tr>
  • 第 4 行:[name] 属性正确;
  • 第 4–6 行:选项生成正确,且已选中正确的选项。

让我们回到生成这些 HTML 代码的源代码:


@Html.DropDownListFor(m => m.DropDownListField, new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
  • 第一个参数是一个 lambda 函数(这就是它的名称),其中 `m` 代表视图模型,而 `m.DropDownListField` 是该模型的一个属性。 HTML 代码生成器将使用该属性的名称来生成即将生成的 [select] 元素的 [id] 和 [name] 属性。如果使用了不存在的属性,错误将在编译时发生,而非运行时。这比之前的解决方案有所改进,因为之前的方案仅在运行时检测命名错误;
  • 第二个参数用于指定将填充下拉列表的元素集合。[SelectList] 类允许您构建此集合:
    • 其第一个参数是任意类型的元素集合。此处我们使用的是 [Item] 类型的集合;
    • 其第二个参数是元素的属性,该属性将为 <option> 标签提供值。此处是 [Item] 类的 [Value] 属性;
    • 其第三个参数是元素的属性,该属性将提供 <option> 标签的标签。此处,它是 [Item] 类的 [Label] 属性;
  • 为了确定应选择哪个选项(即 selected 属性),框架的做法与我们一致:它会将选项的值与 [DropDownListField] 属性的当前值进行比较。

现在让我们看看还可以使用哪些其他方法:

单选按钮

新的代码如下:


        <!-- les boutons radio -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
            @{
    foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
    {
              @Html.RadioButtonFor(m => m.RadioButtonField, @item.Value)@item.Label
              <text/>
    }
            }
          </td>
</tr>

生成的 HTML 代码如下:


        <!-- radio buttons -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
<input id="RadioButtonField" name="RadioButtonField" type="radio" value="1" />oui              
<input checked="checked" id="RadioButtonField" name="RadioButtonField" type="radio" value="2" />non              
          </td>
</tr>

所使用的方法是 [Html.RadioButtonFor]:

@Html.RadioButtonFor(m => m.RadioButtonField, @item.Value)
  • 第一个参数是与单选按钮关联的模型属性(即 [name] 属性);
  • 第二个参数是要赋予单选按钮的值(即 [value] 属性)。

复选框

代码更改如下:


        <!-- les cases à cocher -->
        <tr>
          <td>Cases à cocher</td>
          <td>
            @{
              @Html.CheckBoxFor(m=>m.CheckBoxField1) @Model.CheckBoxesFieldItems[0].Label
              @Html.CheckBoxFor(m=>m.CheckBoxField2) @Model.CheckBoxesFieldItems[1].Label
              @Html.CheckBoxFor(m=>m.CheckBoxField3) @Model.CheckBoxesFieldItems[2].Label
            }
</td>

用于生成复选框的方法是 [Html.CheckBoxFor]:

Html.CheckBoxFor(m=>m.Propriété)

该参数是模型中将与复选框关联的布尔属性。如果 [Property=true],复选框将被选中;如果 [Property=false],复选框将不被选中。在所有情况下,[value] 属性均被设置为 true。生成的 HTML 代码如下:


<input id="Propriété" name="Propriété" type="checkbox" value="true" />
<input name="Propriété" type="hidden" value="false" />
  • 第 1 行:带有 [value="true"] 属性的复选框;
  • 第 2 行:一个隐藏字段(type=hidden),其名称 [Property] 与复选框相同,并带有 [value="false"] 属性。为什么会有两个名称相同的 [input] 标签?有两种情况:
  • 第 1 行中的复选框被选中。此时提交的参数字符串为 Property=true&Property=false(第 1 行和第 2 行)。由于属性 [Property] 仅期望一个值,人们可能会认为框架会将值 [true] 赋给 [Property]。它只需对接收到的值进行逻辑或运算即可实现这一点;
  • 第 1 行复选框未被选中。此时,提交的参数字符串为 Property=false(仅第 2 行),属性 [Property] 被赋值为 [false],这是正确的(因为复选框未被选中)。

单行输入字段

新代码如下:


          <!-- le champ de saisie texte monoligne -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              @Html.TextBoxFor(m => m.TextField, new { size = "30" })
            </td>
</tr>

生成的 HTML 代码如下:


          <!-- single-line text input field -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input id="TextField" name="TextField" size="30" type="text" value="quelques mots" />
            </td>
</tr>

所采用的方法如下:


@Html.TextBoxFor(m => m.TextField, new { size = "30" })

  • 第一个参数指定了与输入字段关联的模型属性。该属性名称将用于生成的 <input> 标签的 [name] 和 [id] 属性中,其值将被赋给 [value] 属性;
  • 第二个参数是一个匿名类,用于指定生成的 HTML 标签的某些属性,在本例中是 [size] 属性。

密码输入框

新代码如下:


        <!-- le champ de saisie d'un mot de passe -->
        <tr>
          <td>Mot de passe</td>
          <td>
            @Html.PasswordFor(m => m.PasswordField, new { size = "15" })
          </td>
</tr>

生成的 HTML 代码如下:


        <!-- password entry field -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input id="PasswordField" name="PasswordField" size="15" type="password" />
          </td>
</tr>

所采用的方法如下:


@Html.PasswordFor(m => m.PasswordField, new { size = "15" })

其行为与 [Html.TextBoxFor] 方法类似。

多行输入字段

新代码如下:


        <!-- le champ de saisie texte multilignes -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            @Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })
          </td>
</tr>

生成的 HTML 代码如下:


        <!-- le champ de saisie texte multilignes -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea cols="30" id="TextAreaField" name="TextAreaField" rows="5">
ligne1
ligne2</textarea>
          </td>
</tr>

所采用的方法如下:


@Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })

其行为与 [Html.TextBoxFor] 方法类似。

单选列表

新代码如下:


        <!-- single-choice list -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
          @Html.DropDownListFor(m => m.SimpleChoiceListField, new SelectList(@Model.SimpleChoiceListFieldItems, "Value", "Label"), new { size = "3" })
</tr>

生成的 HTML 代码如下:


        <!-- single-choice list -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
          <select id="SimpleChoiceListField" name="SimpleChoiceListField" size="3">
<option value="1">liste1</option>
<option value="2">liste2</option>
<option selected="selected" value="3">liste3</option>
<option value="4">liste4</option>
<option value="5">liste5</option>
</select>
</tr>

我们已经介绍了 [Html.DropDownListFor] 方法。这里唯一的区别在于第三个参数,它用于指定一个不为 1 的 [size] 属性。正是这一特性将下拉列表 [size=1] 转换为一个简单的列表。

多选列表

新代码如下:


        <!-- multiple choice list -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
          @Html.ListBoxFor(m => m.MultipleChoiceListField, new SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })
</tr>

生成的 HTML 代码如下:


        <!-- multiple choice list -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
          <select id="MultipleChoiceListField" multiple="multiple" name="MultipleChoiceListField" size="5">
<option selected="selected" value="1">liste1</option>
<option value="2">liste2</option>
<option selected="selected" value="3">liste3</option>
<option value="4">liste4</option>
<option value="5">liste5</option>
</select>
</tr>

该方法


@Html.ListBoxFor(m => m.MultipleChoiceListField, new SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })

的工作原理与 [Html.DropDownListFor] 方法类似,不同之处在于它会生成一个多选列表。所选选项是那些其值(value 属性)位于 [MultipleChoiceListField] 数组中的选项。

<form> 标签也可以通过方法生成:


  @using (Html.BeginForm("Action09Post", "First"))
  {
...
  }

生成的 HTML 代码如下:


<form action="/First/Action09Post" method="post">    
    ...
</form>

该方法


Html.BeginForm("Action09Post", "First")

方法将操作名称作为第一个参数,控制器名称作为第二个参数。

5.7.2. 操作与模型

该表单将由以下 [Action09Get] 操作进行渲染:


    // Action09-GET
    [HttpGet]
    public ViewResult Action09Get(ApplicationModel application)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return View("Formulaire2", new ViewModel09(application));
}

第 6 行渲染的视图是 [Form2],它关联了以下 [ViewModel09] 模型:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;
 
namespace Exemple_03.Models
{
  public class ViewModel09
  {
    // input fields
    public string RadioButtonField { get; set; }
    public bool CheckBoxField1 { get; set; }
    public bool CheckBoxField2 { get; set; }
    public bool CheckBoxField3 { get; set; }
    public string TextField { get; set; }
    public string PasswordField { get; set; }
    public string TextAreaField { get; set; }
    public string DropDownListField { get; set; }
    public string SimpleChoiceListField { get; set; }
    public string[] MultipleChoiceListField { get; set; }
 
    // collections to be displayed in the form
    public ApplicationModel.Item[] RadioButtonFieldItems { get; set; }
    public ApplicationModel.Item[] CheckBoxesFieldItems { get; set; }
    public ApplicationModel.Item[] DropDownListFieldItems { get; set; }
    public ApplicationModel.Item[] SimpleChoiceListFieldItems { get; set; }
    public ApplicationModel.Item[] MultipleChoiceListFieldItems { get; set; }
 
    // manufacturers
    public ViewModel09()
    {
    }
 
    public ViewModel09(ApplicationModel application)
    {
      // initialization collections
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // field initialization
      RadioButtonField = "2";
      CheckBoxField2 = true;
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
      MultipleChoiceListField = new string[] { "1", "3" };
    }
  }
}

[ViewModel09] 与 [ViewModel08] 的区别在于对复选框的处理方式。它不再使用包含三个复选框的数组,而是使用了三个独立的复选框(第 11–13 行)。

该表单将由以下 [Action09Post] 操作进行处理:


    // Action09-POST
    [HttpPost]
    public ViewResult Action09Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      ViewModel09 modèle = new ViewModel09(application);
      TryUpdateModel(modèle, posted);
      // processing of non-posted values
      if (posted["SimpleChoiceListField"] == null)
      {
        modèle.SimpleChoiceListField = "";
      }
      if (posted["MultipleChoiceListField"] == null)
      {
        modèle.MultipleChoiceListField = new string[] { };
      }
      // form display
      return View("Formulaire2", modèle);
}

[Action09Post] 操作与 [Action08Post] 操作完全相同,仅有两点不同:

  • 第 18 行:使用 [Form2] 视图代替 [Form] 视图;
  • 不再处理未选中的复选框。现在由 [Html.CheckBoxFor] 方法正确处理。

5.8. 根据模型的元数据生成表单

除了上述方法外,还有其他生成表单的方法。其中一种方法是将信息与模型属性关联,以此告知 MVC 框架应生成哪种输入标签。这种信息被称为元数据。

请看以下视图模型 [ViewModel10]:


using System;
using System.ComponentModel.DataAnnotations;
using System.Drawing;
 
namespace Exemple_03.Models
{
  public class ViewModel10
  {
    [Display(Name="Text")]
    [DataType(DataType.Text)]
    public string Text { get; set; }
 
    [Display(Name = "TextArea")]
    [DataType(DataType.MultilineText)]
    public string MultiLineText { get; set; }
 
    [Display(Name = "Number")]
    public int Number { get; set; }
 
    [Display(Name = "Decimal")]
    [UIHint("Decimal")]
    public double Decimal { get; set; }
 
    [Display(Name = "Tel")]
    [DataType(DataType.PhoneNumber)]
    public string Tel { get; set; }
 
    [Display(Name = "Date")]
    [DataType(DataType.Date)]
    public DateTime Date { get; set; }
 
    [Display(Name = "Time")]
    [DataType(DataType.Time)]
    public DateTime Time { get; set; }
 
    [Display(Name = "HiddenInput")]
    [UIHint("HiddenInput")]
    public string HiddenInput { get; set; }
 
    [Display(Name = "Boolean")]
    [UIHint("Boolean")]
    public bool Boolean { get; set; }
 
    [Display(Name = "Email")]
    [DataType(DataType.EmailAddress)]
    public string Email{ get; set; }
 
    [Display(Name = "Url")]
    [DataType(DataType.Url)]
    public string Url { get; set; }
 
    [Display(Name = "Password")]
    [DataType(DataType.Password)]
    public string Password { get; set; }
 
    [Display(Name = "Currency")]
    [DataType(DataType.Currency)]
    public double Currency { get; set; }
 
    [Display(Name = "CreditCard")]
    [DataType(DataType.CreditCard)]
    public string CreditCard { get; set; }
 
    // manufacturer
    public ViewModel10()
    {
      Text = "tra la la";
      MultiLineText = "ligne1\nligne2";
      Number = 4;
      Decimal = 10.2;
      Tel = "0617181920";
      Date = DateTime.Now;
      Time = DateTime.Now;
      HiddenInput = "caché";
      Boolean = true;
      Email = "x@y.z";
      Url = "http://istia.univ-angers.fr";
      Password = "mdp";
      Currency = 4.2;
      CreditCard = "0123456789012345";
    }
  }
}

元数据由 [Display, DataType, UIHint] 标签组成。

该视图模板将由以下 [Action10Get] 操作构建:


    // Action10-GET
    [HttpGet]
    public ViewResult Action10Get()
    {
      return View(new ViewModel10());
}

在上文第 5 行中,我们指定了该操作 [/First/Action10Get.cshtml] 的默认视图,用于显示类型为 [ViewModel10] 的视图模型。该视图如下所示:


@model Exemple_03.Models.ViewModel10
 
@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action10Get</title>
</head>
<body>
  <h3>Formulaire ASP.NET MVC - 2</h3>
  @using (Html.BeginForm("Action10Post", "First"))
  {
    <table>
      <thead>
        <tr>
          <th>LabelFor</th>
          <th>EditorFor</th>
          <th>DisplayFor</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>@Html.LabelFor(m => m.Text)</td>
          <td>@Html.EditorFor(m => m.Text)</td>
          <td>@Html.DisplayFor(m => m.Text)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.MultiLineText)</td>
          <td>@Html.EditorFor(m => m.MultiLineText)</td>
          <td>@Html.DisplayFor(m => m.MultiLineText)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Number)</td>
          <td>@Html.EditorFor(m => m.Number)</td>
          <td>@Html.DisplayFor(m => m.Number)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Decimal)</td>
          <td>@Html.EditorFor(m => m.Decimal)</td>
          <td>@Html.DisplayFor(m => m.Decimal)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Tel)</td>
          <td>@Html.EditorFor(m => m.Tel)</td>
          <td>@Html.DisplayFor(m => m.Tel)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Date)</td>
          <td>@Html.EditorFor(m => m.Date)</td>
          <td>@Html.DisplayFor(m => m.Date)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Time)</td>
          <td>@Html.EditorFor(m => m.Time)</td>
          <td>@Html.DisplayFor(m => m.Time)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.HiddenInput)</td>
          <td>@Html.EditorFor(m => m.HiddenInput)</td>
          <td>@Html.DisplayFor(m => m.HiddenInput)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Boolean)</td>
          <td>@Html.EditorFor(m => m.Boolean)</td>
          <td>@Html.DisplayFor(m => m.Boolean)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Email)</td>
          <td>@Html.EditorFor(m => m.Email)</td>
          <td>@Html.DisplayFor(m => m.Email)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Url)</td>
          <td>@Html.EditorFor(m => m.Url)</td>
          <td>@Html.DisplayFor(m => m.Url)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Password)</td>
          <td>@Html.EditorFor(m => m.Password)</td>
          <td>@Html.DisplayFor(m => m.Password)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Currency)</td>
          <td>@Html.EditorFor(m => m.Currency)</td>
          <td>@Html.DisplayFor(m => m.Currency)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.CreditCard)</td>
          <td>@Html.EditorFor(m => m.CreditCard)</td>
          <td>@Html.DisplayFor(m => m.CreditCard)</td>
        </tr>
      </tbody>
    </table>
    <input type="submit" value="Valider" />
  }
</body>
</html>

对于模型的每个属性,我们使用以下方法:

  • Html.LabelFor 来显示该属性 [DisplayName] 元数据的值;
  • Html.EditorFor 用于生成该属性的 HTML 输入标签。此方法使用该属性的 [DataType] 和 [UIHint] 元数据;
  • Html.DisplayFor 用于按 [DataType] 元数据指定的格式显示该属性的值。

以下是在 Chrome 浏览器中该方法的示例:

Image

根据所使用的浏览器不同,页面显示效果可能有所差异。这是因为生成的视图使用了 HTML 5 版本(即 HTML5)引入的新标签。目前并非所有浏览器都支持该版本。在上例中,Chrome 浏览器仅部分支持。

5.8.1. 表单的 [POST]

表单的 [POST] 请求由以下 [Action10Post] 操作处理:


    // Action10-POST
    [HttpPost]
    public ContentResult Action10Post(ViewModel10 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, valide={2}, erreurs={3}", RouteData.Values["controller"], RouteData.Values["action"], ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}
  • 第 3 行:[Action10Post] 操作将提交的表单用作其输入模型;
  • 第 5 行:我们从该表单中获取验证错误;
  • 第 6 行:准备发送给客户端的文本响应;
  • 第 7 行:发送该响应。

现在让我们逐一考察 [ViewModel10] 模型的属性,并了解相关的元数据如何影响生成的 HTML 以及输入字段的验证。

5.8.2. [Text] 属性

定义


    [Display(Name="Text")]
    [DataType(DataType.Text)]
    public string Text { get; set; }
...
Text = "tra la la";

视图


      <tr>
        <td>@Html.LabelFor(m => m.Text)</td>
        <td>@Html.EditorFor(m => m.Text)</td>
        <td>@Html.DisplayFor(m => m.Text)</td>
</tr>

视觉

Image

生成的 HTML


      <tr>
        <td><label for="Text">Text</label></td>
        <td><input class="text-box single-line" id="Text" name="Text" type="text" value="tra la la" /></td>
        <td>tra la la</td>
</tr>

评论

  • [Html.LabelFor] 方法在第 2 行生成了 <label> 标签。[for] 属性的值是 [Html.LabelFor] 方法的参数属性名称


public string Text { get; set; }

标签开始与结束之间显示的文本即为元数据的文本


[Display(Name="Text")]

[Html.LabelFor] 方法始终按此方式工作。对于其他属性,我们将不再赘述。

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签。请注意,该标签具有一个 [class] 属性,将 CSS 类 [text-box single-line] 与该标签关联起来。 [id] 和 [name] 属性的值为 [Text],这是 [Html.EditorFor] 方法的参数属性的名称。由于元数据的原因,[type] 属性的值为 [text]


[DataType(DataType.Text)]

  • [Html.DisplayFor] 方法在第 4 行生成了文本。这是 [Html.DisplayFor] 方法的参数属性的值。该方法受元数据的影响


[DataType(DataType.Text)]

,因此该值将作为未格式化的文本显示。

5.8.3. [MultiLineText] 属性

定义


    [Display(Name = "TextArea")]
    [DataType(DataType.MultilineText)]
public string MultiLineText { get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.MultiLineText)</td>
        <td>@Html.EditorFor(m => m.MultiLineText)</td>
        <td>@Html.DisplayFor(m => m.MultiLineText)</td>
</tr>

可视化

Image

生成的 HTML


      <tr>
        <td><label for="MultiLineText">TextArea</label></td>
        <td><textarea class="text-box multi-line" id="MultiLineText" name="MultiLineText">
ligne1
ligne2</textarea></td>
        <td>ligne1
ligne2</td>
</tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <textarea> 标签。请注意,该标签带有 [class] 属性,用于将 CSS 类 [text-box multi-line] 与该标签关联。其 [id] 和 [name] 属性的值均为 [MultiLineText],这是 [Html.EditorFor] 方法的参数属性名称。这种情况总是如此。 我们不再赘述。由于元数据的存在,生成的标签为 <textarea>


[DataType(DataType.MultilineText)]

,该元数据指定了该属性为多行文本。

  • [Html.DisplayFor] 方法生成了第 4–5 行的文本。这是 [Html.DisplayFor] 方法的 parameter 属性的值。

5.8.4. [Number] 属性

定义


    [Display(Name = "Number")]
public int Number { get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.Number)</td>
        <td>@Html.EditorFor(m => m.Number)</td>
        <td>@Html.DisplayFor(m => m.Number)</td>
</tr>

可视化

Image

生成的 HTML


<tr>
        <td><label for="Number">Number</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-number="Le champ Number doit être un nombre." data-val-required="Le champ Number est requis." id="Number" name="Number" type="number" value="4" /></td>
        <td>4</td>
      </tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签,其 [type] 属性被设置为 [number]。显然,这仅仅是因为该属性具有 [int] 类型。HTML5 不识别 [data-val]、[data-val-number] 和 [data-val-required] 这些属性。它们是由客户端 JavaScript 数据验证框架使用的;
  • [Html.DisplayFor] 方法在第 4 行生成了文本,即该属性的值。

验证

[data-x] 属性影响客户端数据验证。以下是两个示例:

我们输入一个错误的数字并提交:

Image

在上例中,验证是在客户端进行的。在错误得到纠正之前,表单将不会被提交。

另一个示例:我们未输入任何内容:

在上文的 [1] 中,[Action10Post] 报告了错误。您可能还记得,我们之前是通过在待验证的属性(此处为 [Number] 属性)上使用 [Required] 属性来实现此行为的(参见63),而在这里,我们无需这样做。

5.8.5. [Decimal] 属性

定义


    [Display(Name = "Decimal")]
    [UIHint("Decimal")]
public double Decimal { get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.Decimal)</td>
        <td>@Html.EditorFor(m => m.Decimal)</td>
        <td>@Html.DisplayFor(m => m.Decimal)</td>
</tr>

可视化

Image

生成的 HTML


      <tr>
        <td><label for="Decimal">Decimal</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-number="Le champ Decimal doit être un nombre." data-val-required="Le champ Decimal est requis." id="Decimal" name="Decimal" type="text" value="10,20" /></td>
        <td>10,20</td>
</tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签,其 [type] 属性类型为 [text]。其他属性与之前生成的 [Number] 属性完全相同。元数据:


[UIHint("Decimal")]

会导致该属性值在 [Html.EditorFor] 和 [Html.DisplayFor] 方法中均以小数点后两位的形式显示

验证

与前一个案例不同,客户端未报告任何验证错误。该错误仅由 [Action10Post] 操作报告。同样,十进制数字为必填项,无需设置 [Required] 属性。

5.8.6. [Tel] 属性

定义


    [Display(Name = "Tel")]
    [DataType(DataType.PhoneNumber)]
public string Tel { get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.Tel)</td>
        <td>@Html.EditorFor(m => m.Tel)</td>
        <td>@Html.DisplayFor(m => m.Tel)</td>
</tr>

可视化

Image

生成的 HTML


      <tr>
        <td><label for="Tel">Tel</label></td>
        <td><input class="text-box single-line" id="Tel" name="Tel" type="tel" value="0617181920" /></td>
        <td>0617181920</td>
</tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签,其 [type] 属性类型为 [tel]。该值是根据元数据生成的:


[DataType(DataType.PhoneNumber)]

<input> 标签的 [tel] 类型是 HTML5 中的新特性。Chrome 浏览器将其视为 [type] 属性为 [text] 的 <input> 标签。

验证

客户端和服务器端均未报告任何验证错误。您可以输入任意内容。

5.8.7. [Date] 属性

定义


    [Display(Name = "Date")]
    [DataType(DataType.Date)]
public DateTime Date { get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.Date)</td>
        <td>@Html.EditorFor(m => m.Date)</td>
        <td>@Html.DisplayFor(m => m.Date)</td>
</tr>

可视化

Image

生成的 HTML


      <tr>
        <td><label for="Date">Date</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-date="Le champ Date doit être une date." data-val-required="Le champ Date est requis." id="Date" name="Date" type="date" value="11/10/2013" /></td>
        <td>11/10/2013</td>
</tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签,其 [type] 属性类型为 [date]。该值是根据元数据生成的:


[DataType(DataType.Date)]

<input> 标签的 [date] 类型是 HTML5 中的新特性。Chrome 浏览器能够识别该类型,并允许用户通过日历输入日期。此外,输入的日期将以 [dd/mm/yyyy] 格式显示,这意味着 Chrome 会根据浏览器的 [locale] 调整日期格式。

  • [Html.DisplayFor] 方法同样以 [dd/mm/yyyy] 格式写入了日期,这同样归因于 [Date] 元数据的存在。

验证

客户端会标记无效日期 [1],从而阻止表单提交至服务器。

客户端不会对缺少日期的情况进行标记,但服务器端会进行标记 [2]。

5.8.8. [Time] 属性

定义


    [Display(Name = "Time")]
    [DataType(DataType.Time)]
public DateTime Time { get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.Time)</td>
        <td>@Html.EditorFor(m => m.Time)</td>
        <td>@Html.DisplayFor(m => m.Time)</td>
</tr>

可视化

Image

生成的 HTML


      <tr>
        <td><label for="Time">Time</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-required="Le champ Time est requis." id="Time" name="Time" type="time" value="11:17" /></td>
        <td>11:17</td>
</tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签,其 [type] 属性类型为 [time]。该值是根据元数据生成的:


[DataType(DataType.Time)]

<input> 标签的 [time] 类型是 HTML5 中的新特性。Chrome 浏览器能够识别该类型,并允许您以 [hh:mm] 格式输入时间;

  • [Html.DisplayFor] 方法同样以 [hh:mm] 格式显示了时间,这同样是由于存在 [Time] 元数据所致。

验证

从技术上讲,无法输入无效的时间。如果缺少时间,服务器端会发出警告:

Image

5.8.9. [HiddenInput] 属性

定义


    [Display(Name = "HiddenInput")]
    [UIHint("HiddenInput")]
public string HiddenInput { get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.HiddenInput)</td>
        <td>@Html.EditorFor(m => m.HiddenInput)</td>
        <td>@Html.DisplayFor(m => m.HiddenInput)</td>
</tr>

视觉

Image

生成的 HTML


      <tr>
        <td><label for="HiddenInput">HiddenInput</label></td>
        <td>cach&#233;<input id="HiddenInput" name="HiddenInput" type="hidden" value="caché" /></td>
        <td>cach&#233;</td>
</tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签,其 [type] 属性设置为 [hidden],即一个隐藏字段(但该字段仍会被提交)。此值是根据元数据生成的:


[UIHint("HiddenInput")]

  • [Html.DisplayFor] 方法写入了该隐藏字段的值。

5.8.10. [Boolean] 属性

定义


    [Display(Name = "Boolean")]
public bool Boolean { get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.Boolean)</td>
        <td>@Html.EditorFor(m => m.Boolean)</td>
        <td>@Html.DisplayFor(m => m.Boolean)</td>
</tr>

视觉

Image

生成的 HTML


      <tr>
        <td><label for="Boolean">Boolean</label></td>
        <td><input checked="checked" class="check-box" data-val="true" data-val-required="Le champ Boolean est requis." id="Boolean" name="Boolean" type="checkbox" value="true" /><input name="Boolean" type="hidden" value="false" /></td>
        <td><input checked="checked" class="check-box" disabled="disabled" type="checkbox" /></td>
</tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签,其 [type] 属性设置为 [checkbox],即复选框。生成此值是因为该属性是布尔型:


public bool Boolean { get; set; }

  • [Html.DisplayFor] 方法生成了第 4 行,该行同样是一个复选框(type 属性),但处于禁用状态(disabled 属性)。

5.8.11. [Email] 属性

定义


    [Display(Name = "Email")]
    [DataType(DataType.EmailAddress)]
public string Email{ get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.Email)</td>
        <td>@Html.EditorFor(m => m.Email)</td>
        <td>@Html.DisplayFor(m => m.Email)</td>
</tr>

可视化

Image

生成的 HTML


      <tr>
        <td><label for="Email">Email</label></td>
        <td><input class="text-box single-line" id="Email" name="Email" type="email" value="x@y.z" /></td>
        <td><a href="mailto:x@y.z">x@y.z</a></td>
</tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签,其 [type] 属性类型为 [email]。该类型是 HTML5 中的新特性。生成此类型是因为元数据:


[DataType(DataType.EmailAddress)]

Chrome 似乎将此类型视为 [text] 类型。

  • [Html.DisplayFor] 方法生成了第 4 行:一个指向该电子邮件地址的链接。

验证

客户端报告了一个无效的地址 [1]:

留空该字段不会引发错误。

5.8.12. [Url] 属性

定义


    [Display(Name = "Url")]
    [DataType(DataType.Url)]
public string Url { get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.Url)</td>
        <td>@Html.EditorFor(m => m.Url)</td>
        <td>@Html.DisplayFor(m => m.Url)</td>
</tr>

可视化

Image

生成的 HTML


      <tr>
        <td><label for="Url">Url</label></td>
        <td><input class="text-box single-line" id="Url" name="Url" type="url" value="http://istia.univ-angers.fr" /></td>
        <td><a href="http://istia.univ-angers.fr">http://istia.univ-angers.fr</a></td>
</tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签,其 [type] 属性类型为 [url]。该类型是 HTML5 中的新特性。生成该标签是因为元数据:


[DataType(DataType.Url)]

Chrome 似乎将此类型视为 [text] 类型。

  • [Html.DisplayFor] 方法生成了第 4 行:一个指向该 URL 的链接。

验证

客户端报告了一个无效的 URL [1]:

未输入内容不会导致错误。

5.8.13. [密码] 属性

定义


    [Display(Name = "Password")]
    [DataType(DataType.Password)]
public string Password { get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.Password)</td>
        <td>@Html.EditorFor(m => m.Password)</td>
        <td>@Html.DisplayFor(m => m.Password)</td>
</tr>

视觉

Image

生成的 HTML


      <tr>
        <td><label for="Password">Password</label></td>
        <td><input class="text-box single-line password" id="Password" name="Password" type="password" value="mdp" /></td>
        <td>mdp</td>
</tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签,其 [type] 属性的类型为 [password]。该类型是根据元数据生成的:


[DataType(DataType.Password)]

  • [Html.DisplayFor] 方法生成了第 4 行。

5.8.14. [Currency] 属性

定义


    [Display(Name = "Currency")]
    [DataType(DataType.Currency)]
public double Currency { get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.Currency)</td>
        <td>@Html.EditorFor(m => m.Currency)</td>
        <td>@Html.DisplayFor(m => m.Currency)</td>
</tr>

可视化

Image

生成的 HTML


      <tr>
        <td><label for="Currency">Currency</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-number="Le champ Currency doit être un nombre." data-val-required="Le champ Currency est requis." id="Currency" name="Currency" type="text" value="4,2" /></td>
        <td>4,20 €</td>
</tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签,其 [type] 属性设置为 [text];

  • [Html.DisplayFor] 方法生成了第 4 行,即一个带有两位小数和货币符号的数字。使用此格式是因为元数据:


[DataType(DataType.Currency)]

验证

服务器端报告了无效值 [1] 或缺失值 [2]:

5.8.15. [CreditCard] 属性

定义


    [Display(Name = "CreditCard")]
    [DataType(DataType.CreditCard)]
public string CreditCard { get; set; }

视图


      <tr>
        <td>@Html.LabelFor(m => m.CreditCard)</td>
        <td>@Html.EditorFor(m => m.CreditCard)</td>
        <td>@Html.DisplayFor(m => m.CreditCard)</td>
</tr>

可视化

Image

生成的 HTML


      <tr>
        <td><label for="CreditCard">CreditCard</label></td>
        <td><input class="text-box single-line" id="CreditCard" name="CreditCard" type="text" value="0123456789012345" /></td>
        <td>0123456789012345</td>
</tr>

评论

  • [Html.EditorFor] 方法在第 3 行生成了 <input> 标签,其 [type] 属性类型为 [text]。第 4 行由 [Html.DisplayFor] 方法生成。此处尚不清楚元数据起到了什么作用:


[DataType(DataType.CreditCard)]

验证

无论在客户端还是服务器端,均不进行任何验证。

5.9. 表单验证

我们在第 4.5 节及后续章节中已经讨论了验证操作模型的问题。接下来,我们将结合表单的场景重新探讨这一问题:

  • 如何向用户通知输入错误;
  • 在客户端和服务器端同时进行验证,以便更快地向用户报告错误。

5.9.1. 服务器端验证

考虑以下模型:


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Net.Mail;
 
namespace Exemple_03.Models
{
  public class ViewModel11 : IValidatableObject
  {
 
    [Required(ErrorMessage = "Information requise")]
    [Display(Name = "Chaîne d'au moins quatre caractères")]
    [RegularExpression(@"^.{4,}$", ErrorMessage = "Information incorrecte")]
    public string Chaine1 { get; set; }
 
    [Display(Name = "Chaîne d'au plus quatre caractères")]
    [Required(ErrorMessage = "Information requise")]
    [RegularExpression(@"^.{1,4}$", ErrorMessage = "Information incorrecte")]
    public string Chaine2 { get; set; }
 
    [Required(ErrorMessage = "Information requise")]
    [Display(Name = "Chaîne de quatre caractères exactement")]
    [RegularExpression(@"^.{4,4}$", ErrorMessage = "Information incorrecte")]
    public string Chaine3 { get; set; }
 
    [Required(ErrorMessage = "Information requise")]
    [Display(Name = "Nombre entier")]
    public int Entier1 { get; set; }
 
    [Display(Name = "Nombre entier dans l'intervalle [1,100]")]
    [Required(ErrorMessage = "Information requise")]
    [Range(1, 100, ErrorMessage = "Information incorrecte")]
    public int Entier2 { get; set; }
 
    [Display(Name = "Nombre réel")]
    [Required(ErrorMessage = "Information requise")]
    public double Reel1 { get; set; }
 
    [Display(Name = "Nombre réel dans l'intervalle [10.2, 11.3]")]
    [Required(ErrorMessage = "Information requise")]
    [Range(10.2, 11.3, ErrorMessage = "Information incorrecte")]
    public double Reel2 { get; set; }
 
    [Display(Name = "Adresse mail")]
    [Required(ErrorMessage = "Information requise")]
    public string Email1 { get; set; }
 
    [Display(Name = "Date sous la forme dd/jj/aaaa")]
    [RegularExpression(@"\s*\d{2}/\d{2}/\d{4}\s*", ErrorMessage = "Information incorrecte")]
    [Required(ErrorMessage = "Information requise")]
    public string Regexp1 { get; set; }
 
    [Display(Name = "Date postérieure à celle d'aujourd'hui")]
    [Required(ErrorMessage = "Information requise")]
    [DataType(DataType.Date)]
    public DateTime Date1 { get; set; }
 
    // validation
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      // Date 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
      }
      // Email1
      try
      {
        new MailAddress(Email1);
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
      }
      // return the list of errors
      return résultats;
    }
  }
}

该模型将由以下视图 [Action11Get.cshtml] 显示:


@model Exemple_03.Models.ViewModel11
@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action11Get</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
</head>
<body>
  <h3>Formulaire ASP.NET MVC – Validation 1</h3>
  @using (Html.BeginForm("Action11Post", "First"))
  {
    <table>
      <thead>
        <tr>
          <th>Type attendu</th>
          <th>Valeur saisie</th>
          <th>Message d'erreur</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>@Html.LabelFor(m => m.Chaine1)</td>
          <td>@Html.EditorFor(m => m.Chaine1)</td>
          <td>@Html.ValidationMessageFor(m => m.Chaine1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Chaine2)</td>
          <td>@Html.EditorFor(m => m.Chaine2)</td>
          <td>@Html.ValidationMessageFor(m => m.Chaine2)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Chaine3)</td>
          <td>@Html.EditorFor(m => m.Chaine3)</td>
          <td>@Html.ValidationMessageFor(m => m.Chaine3)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Entier1)</td>
          <td>@Html.EditorFor(m => m.Entier1)</td>
          <td>@Html.ValidationMessageFor(m => m.Entier1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Entier2)</td>
          <td>@Html.EditorFor(m => m.Entier2)</td>
          <td>@Html.ValidationMessageFor(m => m.Entier2)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Reel1)</td>
          <td>@Html.EditorFor(m => m.Reel1)</td>
          <td>@Html.ValidationMessageFor(m => m.Reel1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Reel2)</td>
          <td>@Html.EditorFor(m => m.Reel2)</td>
          <td>@Html.ValidationMessageFor(m => m.Reel2)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Email1)</td>
          <td>@Html.EditorFor(m => m.Email1)</td>
          <td>@Html.ValidationMessageFor(m => m.Email1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Regexp1)</td>
          <td>@Html.EditorFor(m => m.Regexp1)</td>
          <td>@Html.ValidationMessageFor(m => m.Regexp1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Date1)</td>
          <td>@Html.EditorFor(m => m.Date1)</td>
          <td>@Html.ValidationMessageFor(m => m.Date1)</td>
        </tr>
      </tbody>
    </table>
    <p>
      <input type="submit" value="Valider" />
    </p>
  }
</body>
</html>

  • 第 12 行:引用了 [Site.css] 样式表。默认情况下,其中包含用于突出显示表单输入错误的类;
  • 第 18–25 行:一个三列表格:
    • 第 1 列使用 [Html.LabelFor] 方法显示文本;
    • 第 2 列使用 [Html.EditorFor] 方法显示输入内容;
    • 第 3 列使用 [Html.ValidationMessageFor] 方法显示输入错误;

使用 [Action11Get] 操作来显示表单:


    // Action11-GET
    [HttpGet]
    public ViewResult Action11Get()
    {
      return View("Action11Get", new ViewModel11());
}

[Action11Post] 操作用于在存在输入错误时重新显示表单:


    // Action11-POST
    [HttpPost]
    public ViewResult Action11Post(ViewModel11 modèle)
    {
      return View("Action11Get", modèle);
}

  • 第 3 行:创建 [ViewModel11] 模型,并使用提交的值对其进行初始化。此时可能会发生错误。模型中的每个无效属性 P 都会关联一条错误消息。这是表单的 [Html.ValidationMessageFor] 方法返回的消息。

以下是一个执行示例:

Image

Image

以下是另一个示例:

Image

请注意,这两个日期都是错误的(当前日期是 2013 年 10 月 11 日),但系统并未报告这些错误。这些错误会被模型的 [Validate] 方法检测到:


    // validation
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      // Date 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
      }
      // Email1
      try
      {
        new MailAddress(Email1);
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
      }
      // Regexp1
      try
      {
        DateTime.ParseExact(Regexp1, "dd/MM/yyyy", CultureInfo.CreateSpecificCulture("fr-FR"));
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Regexp1" }));
      }
 
      // on rend la liste des erreurs
      return résultats;
}

[Validate] 方法仅在所有属性验证均通过后才会执行。最后一个示例演示了这一点:

Image

5.9.2. 客户端验证

之前的验证均在服务器端进行。这需要客户端与服务器之间进行一次往返通信,用户才能看到错误信息。客户端验证利用 JavaScript 代码尽早通知用户错误,且无论如何都会在 POST 请求之前完成。只有在所有检测到的错误都已更正后,POST 请求才会发生。

我们将继续使用之前的 [ViewModel11] 模型,但现在将通过以下 [Action12Get.cshtml] 视图进行展示:


@model Exemple_03.Models.ViewModel11
@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action12Get</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js" ></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js" ></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js" ></script>
</head>
<body>
  <h3>Formulaire ASP.NET MVC - Validation 1</h3>
  @using (Html.BeginForm("Action11Post", "First"))
  {
    <table>
      <thead>
        <tr>
          <th>Type attendu</th>
          <th>Valeur saisie</th>
          <th>Message d'erreur</th>
        </tr>
      </thead>
      <tbody>
...
      </tbody>
    </table>
    <p>
      <input type="submit" value="Valider" />
    </p>
  }
</body>
</html>

注意:第 13 行——请根据您所使用的 Visual Studio 版本调整 jQuery 版本(见下文)。

客户端验证要求在应用程序的 [Web.config] 文件中包含下面的第 3 行。


  <appSettings>
    ...
    <add key="ClientValidationEnabled" value="true" />
</appSettings>

  • 第 1–4 行:[appSettings] 部分必须是 [Web.config] 文件中 [configuration] 部分的直接子元素;

[Action12Get] 视图与之前的 [Action11Get] 视图完全相同,仅第 13–15 行有所不同。这些行包含了视图中进行客户端验证所需的 JavaScript 脚本。这些脚本位于项目的 [Scripts] 文件夹中:

每个脚本都有一个常规版本 [.js] 和一个压缩版本 [min.js]。后者体积更小但难以阅读,通常用于生产环境;而可读版本则用于开发环境。

[Action12Get.cshtml] 视图将由以下 [Action12Get] 操作调用显示:


    // Action12-GET
    [HttpGet]
    public ViewResult Action12Get()
    {
      return View("Action12Get", new ViewModel11());
}

提交的表单将由以下 [Action12Post] 操作进行处理:


    // Action12-POST
    [HttpPost]
    public ViewResult Action12Post(ViewModel11 modèle)
    {
      return View("Action12Get", modèle);
}

让我们通过一个示例来看看它是如何工作的:

只要你在 [1] 中输入一个字符,[2] 中的提示信息就会出现,因为预期值必须至少有四个字符长。因此,每次输入新字符时都会进行验证。输入第四个字符后,错误提示会消失。完成这些操作后,让我们提交表单:

URL [3] 显示 [POST] 请求并未发生。但点击 [Validate] 按钮触发了所有客户端验证,并出现了新的错误信息。

让我们以第一个输入项为例,查看生成的 HTML 代码:


        <tr>
          <td><label for="Chaine1">Cha&#238;ne d&#39;au moins quatre caract&#232;res</label></td>
          <td><input class="text-box single-line" data-val="true" data-val-regex="Information incorrecte" data-val-regex-pattern="^.{4,}$" data-val-required="Information requise" id="Chaine1" name="Chaine1" type="text" value="" /></td>
          <td><span class="field-validation-valid" data-valmsg-for="Chaine1" data-valmsg-replace="true"></span></td>
</tr>

  • 第 3 行包含:
    • 当输入内容缺失时的错误提示 [data-val-required],
    • 输入不正确时的错误信息 [data-val-regex],
    • 输入字符串的正则表达式 [data-val-regex-pattern];
  • 第 4 行,其他属性 [data-x] 用于显示任何错误消息;

生成的标签中的 [data-x] 属性由我们嵌入视图中的 JavaScript 调用。如果缺少该 JavaScript,这些属性将被直接忽略,且不会进行客户端验证。其工作原理与前一个示例相同。因此,这种技术被称为 [无侵入式]。

5.10. 处理导航和操作链接

我们将创建以下两个视图,以演示视图内的链接管理:

  • 在 [1] 和 [2] 中,我们有两个导航链接;
  • 在 [3] 中,有一个用于提交表单的操作链接。它不用于导航。

第 1 页由以下 [Action16Get.cshtml] 视图生成:


@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action16Get</title>
  <script>
    function postForm() {
      // on récupère le formulaire du document
      var form = document.forms[0];
      // soumission
      form.submit();
    }
  </script>
</head>
<body>
  <h3>Navigation - page 1</h3>
  <h4>@ViewBag.info</h4>
  @using (Html.BeginForm("Action16Post", "Second"))
  {
    @Html.Label("data", "Tapez un texte")
    @Html.TextBox("data")
    <a href="javascript:postForm()">Valider</a>
  }
  <p>
    @Html.ActionLink("Page 2", "Action17Get", "Second")
  </p>
</body>
</html>

  • 第 22 行:由将渲染视图的操作初始化的信息;
  • 第 23–28 行:一个表单;
  • 第 25 行:[data] 字段的标签;
  • 第 26 行:名为 [data] 的输入字段;
  • 第 27 行:一个 [submit] 链接。点击时,将执行 JavaScript 函数 [postForm](href 属性)。该函数定义在第 12–17 行;
  • 第 14 行:获取文档中第一个表单(即第 23 行中的表单)的引用;
  • 第 16 行:提交此表单。最终效果等同于点击了 [submit] 按钮。表单将提交至第 23 行指定的控制器和操作;
  • 第 30 行:一个导航链接。生成的 HTML 代码如下:


    <a href="/Second/Action17Get">Page 2</a>

所使用的方法是 ActionLink(Text, Action, Controller)

第 2 页由以下视图 [Action17Get.cshtml] 生成:


@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action17Get</title>
</head>
<body>
  <h3>Navigation - Page 2</h3>
  <h4>@ViewBag.info</h4>
  <p>
    @Html.ActionLink("Page 1", "Action16Get", "Second")
  </p>
</body>
</html>

生成这些视图的操作如下:


      // Action16-GET
      [HttpGet]
      public ViewResult Action16Get()
      {
        ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
        return View("Action16Get");
      }
 
      // Action16-POST
      [HttpPost]
      public ViewResult Action16Post(string data)
      {
        ViewBag.info = string.Format("Contrôleur={0}, Action={1}, Data={2}", RouteData.Values["controller"], RouteData.Values["action"], data);
        return View("Action16Get");
      }
 
      // Action17-GET
      [HttpGet]
      public ViewResult Action17Get()
      {
        ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
        return View();
}

  • 第 6 行,[Action16Get] 操作渲染 [Action16Get.cshtml] 视图,即示例中的第 1 页。该视图基于 [ViewBag](第 5 行);
  • 第 19 行:[Action17Get] 操作生成 [Action17Get.cshtml] 视图,即示例中的第 2 页。该视图基于 [ViewBag](第 21 行);
  • 第 11 行:[Action16Post] 操作处理来自 [Action16Get.cshtml] 视图中表单的 POST 请求。它接收名为 [data] 的参数。请注意,这是表单中输入字段的名称;
  • 第 13 行:将信息放入 [ViewBag] 中;
  • 第 14 行:显示 [Action16Get.cshtml] 视图。

建议读者亲自测试此示例。