Skip to content

5. 视图及其模板

5.1. Introduction

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

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

首先,我们仍在同一解决方案内创建一个新项目 [Exemple-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]。该方法支持多种重载。 我们将介绍其中的一些。主要的是以下内容:
 
  • 第一个参数是视图的名称。如果未提供,则使用与生成 [ViewResult] 的操作同名的视图,该视图将在 [/Views/{controller}] 文件夹中查找,其中 {controller} 是控制器名称;
  • 第二个参数是视图的模板。如果未提供,则视图没有模板。

下面的 [Index] 方法:


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

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

然后在其中创建视图 [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# 代码的一些基本规则:

  • 从 HTML 切换到 C# 是在遇到 @ 字符时(第 1 行)。如果该字符引入了一个代码块,则需添加大括号(第 1 行和第 3 行)。如果它引入了一个需要获取其值的变量,则只需写 @variable;
  • 从 C# 切换回 HTML 是在遇到 < 字符时(第 5 行)。 有时需要强制进行这种转换,特别是在页面中包含未使用 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]:

 

收到的 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] 如下:


    // 操作01
    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]:

 

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

上述方法的缺点在于无法在运行前检测错误。因此,如果视图 [Action01.cshtml] 使用代码


<h4>@ViewBag.Info</h4>

,将会引发错误,因为属性 [Info] 不存在。由操作 [Action01] 创建的属性名为 [info]。因此,我们可以使用强类型模型来避免这种不便。

在之前研究的一个示例中,操作如下:


    // 操作10
    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、Jour、Info1、Info2、Info3、erreurs)。 我们将通过视图模型 [ViewModel01] 传递这些信息。由于该模型继承了 [ActionModel03] 的信息,我们将使其从该类派生而来。

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

并将它的命名空间更改为与项目 [Exemple-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, Jour, Info1, Info2, Info3] 的属性;
  • 第 5 行:为其添加属性 [Erreurs]。

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

  • 接受动作模板 [ActionModel03] 作为输入;
  • 并输出视图模板 [ViewModel01]。

其代码如下:


    // 操作02
    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]相关的错误被聚合到字符串[erreurs]中。 方法 [getErrorMessagesFor] 已在第 65 页进行过描述,并被包含在新项目的控制器 [First] 中;
  • 第 5 行:调用方法 [View] 并传入一个参数。该参数是视图模板。由于未指定具体视图,因此将使用默认视图 [/Views/First/Action02]。 视图模型 [ViewModel01] 被实例化,并使用动作模型 [ActionModel03] 的五项信息以及第 4 行构建的 [erreurs] 信息进行初始化。

现在我们构建视图 [/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] 操作的示例。

首先是无参数的情况:

 

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

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

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

  

其内容如下:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_03.Models
{
  [Bind(Exclude="Erreurs")]
  public class ActionModel04
  {
    // ---------------------- 操作 --------------------------------
    [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; }

    // ---------------------- 视图 --------------------------------
    public string Erreurs { get; set; }
  }
}
  • 第 8-28 行:包含完整性约束的业务模型。这些字段也将作为视图的一部分;
  • 第 31 行:视图模型特有的属性。该属性已通过第 5 行的注解从操作模型中排除。

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


    // 操作03
    public ViewResult Action03(ActionModel04 modèle)
    {
      modèle.Erreurs = getErrorMessagesFor(ModelState);
      return View(modèle);
}
  • 第 2 行:[Action03] 接收类型为 [ActionModel04] 的操作模板;
  • 第5行:并将该模板作为视图模板返回;
  • 第 4 行:补充了 [Erreurs] 信息;

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

  • 在 [1] 中:右键单击 [Action03] 的代码,然后选择 [Ajouter une vue];
  • 为 [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]:

 

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

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 行:该视图模型包含一个类型为 [Personne] 的人员数组,该数组在第 12-16 行中定义;
  • 第6-10行:模型构造函数将第5行的属性[Personnes]初始化为包含两个人员的数组。

生成该模型的输出操作如下:[Action04]:


    // 操作04
    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 行使用的类 [Personne] 的命名空间;
  • 第 16-32 行:用于显示模型中人员的 HTML 数组;
  • 第24行:C#代码的开头由@字符标记。[foreach]语句将遍历模型中的所有人员;
  • 第26-27行:< 字符结束C#代码并开始HTML代码。随后再次出现@字符,切换回C#并写入人员姓名。接着再次出现<字符,切换回HTML模式;
  • 第28行:写入该人的年龄。

执行 [Action04] 操作将得到以下结果:

 

视图中的其他元素也可以由集合提供数据:列表(无论是否为下拉列表)、单选按钮、复选框。让我们来看一个显示下拉列表的新示例。

[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 行:一个具有三个属性的 [Personne2] 类;
  • 第 3 行:视图的 [ViewModel05] 模板;
  • 第 5 行:以 [Prénom Nom] 形式显示在下拉列表中的人员列表;
  • 第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 节中介绍。现将其重述如下:

下拉列表
<select size="1" name="cmbValeurs">
<option value="1">选项1</option>
<option selected="selected" value="2">选项2</option>
<option value="3">选项3</option>
</select>
 
HTML标签
<select size=".." name="..">
<option [selected="selected"] value=”v”>...</option>
...
</select>
将位于 <option>...</option> 标签之间的文本显示在列表中
属性
name="cmbValeurs":控件名称。
size="1":可见列表项的数量。size="1" 使该列表相当于一个下拉列表框。
selected="selected":如果列表项包含此关键字,该项在列表中将显示为已选中。在上例中,当下拉列表首次显示时,列表项 choix2 将作为下拉列表的选中项显示。
value=”v”:如果该项被用户选中,则向服务器提交的值即为 [v]。若未设置此属性,则向服务器提交的是显示并选中的文本。

第 17-25 行的代码生成 <option> 标签,这些标签位于第 16 行的 <select> 标签内。

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

调用操作 [Action05]:

  • 在 [1,2] 中,人员以 [Prénom Nom] 的形式显示;
  • 在[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)或底层([Metier, DAO])中,而不能出现在视图中。 即使遵循 MVC 模型,视图中仍可能因计算中间值而包含大量逻辑。这可能意味着所用的模型不够详细。该模型应包含视图所需的最终值,从而避免视图自行计算。 一个优秀的视图应尽量减少逻辑代码,并保持HTML结构的清晰度。若插入过多的C#代码,HTML结构可能会变得难以阅读。

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

5.5. 表单——入门

向用户展示的表单如下:

 

视图模板将使用之前已用过的 [ViewModel05] 模板。显示该视图的操作如下:


    // 操作06-GET
    [HttpGet]
    public ViewResult Action06()
    {
      return View("Action06Get",new ViewModel05());
}
  • 第2行:该操作只能通过命令 HTTP GET 调用;
  • 第 5 行:视图 [/First/Action06Get.cshtml] 将以类型为 [ViewModel05] 的实例作为模板进行显示。

视图 [/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="frmexemple":表单名称——可选
method="...":浏览器用于将表单中收集的值发送至Web服务器的传输方式
action="...":URL,表单中收集的值将发送至此处。
Web表单由 <form>...</form> 标签包围。表单可以有一个名称(name="xx")。表单中所有控件均适用此规则。 表单的目的是收集用户通过键盘/鼠标输入的信息,并将这些信息发送至某个 Web 服务器的 URL。具体是哪个?即 action="URL" 属性中指定的那个。 如果缺少该属性,信息将发送至包含该表单的文档所在的Web服务器。Web客户端可以使用两种不同的方法——POST和GET——向Web服务器发送数据。 method="méthode" 属性(其中 method 等于 GET 或 POST) 该标签中的 <form> 属性会告知浏览器,应采用何种方法将表单中收集的信息发送至由 action="URL" 属性指定的 URL。 当未指定 action="method" 属性时,默认采用 GET 方法。
  • 第 18 行:可见表单的值将通过 HTTP POST 命令发送至 URL [/First/Action06];
  • 第30行:表单必须包含一个类型为[submit]的按钮。该按钮将触发将输入的值发送至由<form>标签的[action]属性指定的URL。

当用户点击[Valider]按钮时,浏览器究竟会传输什么内容?这一点已在第2.5.3.1节中解释过。让我们回顾一下之前的说明:


HTML控件


视觉


返回值

<input type="radio" value="是" name="R1"/>是
<input type="radio" name="R1" value="否" checked="checked"/>否
R1=是
- 用户选中的单选按钮的 value 属性值。
<input type="checkbox" name="C1" value="一"/>1
<input type="checkbox" name="C2" value="二" checked="checked"/>2
<input type="checkbox" name="C3" value="三"/>3
C1=一
C2=二
- 用户勾选的复选框的 value 属性值
<input type="text" name="txtSaisie" size="20" value="几个词"/>
txtSaisie=Web+编程
- 用户在输入框中输入的文本。空格已被替换为 + 符号
<input type="password" name="txtMdp" size="20" value="unMotDePasse"/>
txtMdp=这是秘密
- 用户在输入框中输入的文本
<textarea rows="2" name="areaSaisie" cols="20">
第1行
第2行
第3行
</textarea>
areaSaisie=Web+编程+基础%0D%0A
Web+编程
- 用户在输入框中输入的文本。%OD%OA 是换行标记。空格已被 + 符号替换
<select size="1" name="cmbValeurs">
<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'>列表1</option>
<option value='2'>列表2</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'>列表1</option>
<option value='2'>列表2</option>
<option selected="selected" value='3'>列表3</option>
<option value='4'>列表4</option>
<option value='5'>列表5</option>
</select>
lst2=1
lst2=3
- 用户所选元素的 [value] 属性
<input type="submit" value="发送" name="cmdRenvoyer"/>
 
cmdRenvoyer=发送
- 用于将表单数据发送至服务器的按钮名称及其 value 属性
<input type="hidden" name="secret" value="uneValeur"/>
 
secret=一个值
- 隐藏字段的属性 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]:


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

大家可能还记得,我们之前已经有一个 [Action06] 操作:


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

只要它们处理的命令不同,就可以存在两个同名的操作:

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

管理 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 之后显示的视图中,用户可能希望显示所选人员的 nom 和 prénom,而非其编号。 因此,需要对 POST 的视图及其模型进行调整。

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

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


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

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

  

using System.Web.Mvc;

namespace Exemple_03.Infrastructure
{
  public class SessionModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      // 返回作用域数据 [Session]
      return controllerContext.HttpContext.Session["data"];
    }
  }
}

[SessionModel] 模型与其 binder、[SessionModelBinder] 之间的关联是在 [Global.asax] 中建立的:


public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
      ...

      // 模型绑定器
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
    }
    // 会话
    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 行:将人员数组放入会话中。在接下来的 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 行:显示字符串。

以下是一个执行示例:

5.6. 表单 – 完整示例

我们在第 2.5.2.1 节中研究了以下表单 HTML:

1
 

接下来我们将研究一个显示(GET)该表单的动作 [Action08Get],以及一个处理(POST)用户输入值的动作 [Action08Post]。这是一个典型的架构。

上文提到的视图模板 [1] 将作为类 [ViewModel08] 的实例。该类将同时:

  • 由 GET 针对操作 [Action08Get] 生成的视图模型;
  • 针对请求 POST 的操作 [Action08Post] 的模型。

5.6.1. 作用域模板 [Application]

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


    protected void Application_Start()
    {
....

      // 模型绑定器
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
      ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());

      // 作用域数据[Application]
      Application["data"] = new ApplicationModel();
}
  • 第 7 行:我们将稍后描述的类型 [ApplicationModel] 与第 82 页已介绍的数据绑定器 [ApplicationModelBinder] 相关联;
  • 第 10 行:一个类型为 [ApplicationModel] 的实例被记录在应用程序字典中,并关联到键 [data]。

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


namespace Exemple_03.Models
{
  public class ApplicationModel
  {
    // 要在表单中显示的集合
    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; }

    // 字段和集合的初始化
    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"}
      };
    }
    // 集合中的元素
    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. 视图 [Formulaire] 的模板

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


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;

namespace Exemple_03.Models
{
  public class ViewModel08
  {
    // 输入字段
    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; }

    // 表单中要显示的集合
    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; }

    // 构造函数
    public ViewModel08()
    {
    }

    public ViewModel08(ApplicationModel application)
    {
      // 集合初始化
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // 字段初始化
      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]将获取表单后续行提交的

        <!-- 单选按钮 -->
        <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

(前提是用户勾选了标有 [non] 的选项)。实际上,被提交的是所选选项的 [value] 属性。

  • 第 11 行:[CheckBoxesField] 将获取表单后续行提交的

        <!-- 复选框 -->
        <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] 将获取表单后续行提交的

          <!-- 单行文本输入框 -->
          <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] 将获取表单后续行提交的

        <!-- 密码输入框 -->
        <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] 将获取表单后续行提交的值:

        <!-- 多行文本输入框 -->
        <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] 将获取表单后续行提交的

        <!-- 下拉列表 -->
        <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

(前提是用户选择了选项 [choix1])。此时提交的是所选选项的 [value] 属性。

  • 第 16 行:[SingleChoiceListField] 将获取表单后续行提交的

        <!-- 单选列表 -->
        <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] 将获取表单后续行提交的

        <!-- 多选列表 -->
        <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

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

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


      // 字段初始化
      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 行:勾选了单选按钮的 [non] 选项;
  • 第 3 行:勾选了复选框中的 [2] 选项;
  • 第 4 行:在输入框中输入了 [quelques mots];
  • 第 5 行:输入密码 [secret];
  • 第6行:在多行输入框中输入[ligne1\nligne2];
  • 第7行:从下拉列表中选择选项 [choix2];
  • 第8行:从单选列表中选择选项 [liste3];
  • 第9行:从多选列表中选择了选项[liste1]和[liste3];

我们将假设已执行了 POST 操作,且希望按用户输入的原样返回表单。这通常发生在向用户返回存在错误的表单时,此时表单将按用户输入的原样返回。

5.6.4. 视图 [Formulaire]

视图 [/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>
        <!-- 单选按钮 -->
        <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行:显示单选按钮:
  
  • 第22行:遍历单选按钮显示的集合;
  • 第24行:具有[value]属性的按钮(其值取自[RadioButtonField]属性)必须被选中。为此,该按钮必须具有[checked="checked"]属性;
  • 第25行:生成<input type="radio">标签,其值为[@item.Value],标签名为[@item.Label];
  • 第26行:<text/>标签不是被识别的HTML标签。该标签实际对应的是[Razor]。当遇到该标签时,[Razor]将生成一个换行符。 这不会影响显示的表单,但会影响生成的代码。此时,<input type="radio"> 标签会分在两行,而不是同一行。当通过浏览器查看所显示页面的源代码时,这会使代码更易于阅读;

接下来,我们逐一查看视图中的其他元素:


        <!-- 复选框 -->
        <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];

<!-- 单行文本输入框 -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input type="text" name="TextField" value="@Model.TextField" size="30" />
            </td>
          </tr>
        <!-- 密码输入框 -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="PasswordField" value="@Model.PasswordField" size="30" />
          </td>
        </tr>
        <!-- 多行文本输入框 -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea name="TextAreaField" cols="40" rows="3">@Model.TextAreaField</textarea>
          </td>
        </tr>
  • 第 5、12 行:将模板的值赋给标签的 [value] 属性;
  • 第19行:同上,但语法不同。

        <!-- 下拉列表 -->
        <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="valeur">libellé</option>,其中值为[@item.Value],标签名为[@item.Label];

        <!-- 单选列表 -->
        <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>

解释与下拉列表相同。


        <!-- 多选列表 -->
        <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 行:生成标签 /optionlibellé</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] 形式提供。已提交的参数 [RadioButtonField] 的值通过表达式 posted[" RadioButtonField"] 获取。 此处返回的是字符串或指针 null。若写成 posted[" CheckBoxesField"],则会得到字符串数组或指针 null
  • 为什么不写成:

public ViewResult Action08Post(ApplicationModel application, ViewModel08 posted)

原因有二:

  • 首先,框架会使用无参构造函数实例化模型 [ViewModel08],这将导致模型的集合未被初始化;
  • 其次,我们需要控制模型的初始化来源。已知该模型有四个可能的来源:GET的参数、POST的参数、所用路由的参数以及uploadé文件的参数。 在此,我们希望仅使用提交的值来初始化模型。
  • 第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);
      // 处理未提交的值
      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[] { };
      }
      // 表单显示
      return View("Formulaire", modèle);
    }
  • 第9-20行:检查某些参数是否已被提交。若未提交,则将其初始化为对应用户未输入的默认值。此检查未针对下拉列表进行,因为该列表始终有一个选项被选中,而其他列表则并非如此。

欢迎读者测试此新版本。

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

5.7.1. 新表单

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

让我们回顾一下用于生成表单下拉列表的代码:


        <!-- 下拉列表 -->
        <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 的生成,特别是针对表单的情况。 利用这些类,前面的下拉列表 可编写如下:


        <!-- 下拉列表 -->
        <tr>
          <td>Liste déroulante</td>
          <td>@Html.DropDownListFor(m => m.DropDownListField,
           new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
          </td>
</tr>

下拉列表由第 4-5 行生成。代码明显更简洁。为下拉列表生成的 HTML 代码如下:


        <!-- 下拉列表 -->
        <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.DropDowListField 是该模型的一个属性。 代码生成器 HTML 将使用该属性的名称来生成即将生成的 [select] 中的 [id] 和 [name] 属性。 如果使用不存在的属性,将在编译时报错,而非运行时报错。这相较于之前的解决方案是一项改进,因为之前的方案中命名错误仅在运行时才会被检测到;
  • 第二个参数用于指定将作为下拉列表数据源的元素集合。[SelectList] 类可用于构建该集合:
    • 其第一个参数是任意类型的元素集合。此处使用的是 [Item] 类型的集合;
    • 其第二个参数是元素的属性,该属性将提供 <option> 标签的值。此处是 [Item] 类的 [Value] 属性;
    • 其第三个参数是元素的属性,该属性将提供 <option> 标签的标签文本。此处是类 [Item] 的属性 [Label];
  • 为了确定应选择哪个选项(属性 selected),框架的做法与我们一致:它将选项的值与属性 [DropDownListField] 的当前值进行比较。

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

单选按钮

新代码如下:


        <!-- 单选按钮 -->
        <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 如下:


        <!-- 单选按钮 -->
        <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])。

复选框

代码演变如下:


        <!-- 复选框 -->
        <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é)

该参数是模型的布尔属性,将与复选框相关联。若为 [Propriété=true],则复选框被选中;若为 [Propriété=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),其名称 [Propriété] 与带有 [value="false"] 属性的复选框相同。 为什么会有两个同名的 [input] 标签?有两种情况:
  • 第 1 行的复选框被选中。此时提交的参数字符串为 Propriété=true&Propriété=false(第 1 行和第 2 行)。 由于属性 [Propriété] 仅需一个值,因此可以认为框架将值 [true] 赋给了 [Propriété]。 它只需对接收到的值进行逻辑运算(如 OU)即可实现;
  • 第 1 行中的复选框未被勾选。因此,提交的参数字符串为 Propriété=false(仅第 2 行),此时属性 [Propriété] 接收的值为 [false],这是正确的(因为复选框未被勾选)。

单行输入框

新代码如下:


          <!-- 单行文本输入框 -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              @Html.TextBoxFor(m => m.TextField, new { size = "30" })
            </td>
</tr>

生成的代码 HTML 如下:


          <!-- 单行文本输入框 -->
          <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] 属性。

密码输入框

新代码如下:


        <!-- 密码输入框 -->
        <tr>
          <td>Mot de passe</td>
          <td>
            @Html.PasswordFor(m => m.PasswordField, new { size = "15" })
          </td>
</tr>

生成的代码 HTML 如下:


        <!-- 密码输入框 -->
        <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.TexBoxFor] 类似。

多行输入字段

新代码如下:


        <!-- 多行文本输入框 -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            @Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })
          </td>
</tr>

生成的代码 HTML 如下:


        <!-- 多行文本输入框 -->
        <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.TexBoxFor] 类似。

单选列表

新代码如下:


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

生成的代码 HTML 如下:


        <!-- 单选列表 -->
        <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] 转变为单选列表。

多选列表

新代码如下:


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

生成的代码 HTML 如下:


        <!-- 多选列表 -->
        <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] 相同,只是它会生成一个多选列表。所选选项是那些在数组 [MultipleChoiceListField] 中具有对应值(属性 value)的选项。

<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 行生成的视图是 [Formulaire2],其关联的模型为以下 [ViewModel09]:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;

namespace Exemple_03.Models
{
  public class ViewModel09
  {
    // 输入字段
    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; }

    // 表单中要显示的集合
    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; }

    // 构造函数
    public ViewModel09()
    {
    }

    public ViewModel09(ApplicationModel application)
    {
      // 集合初始化
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // 字段初始化
      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);
      // 处理未提交的值
      if (posted["SimpleChoiceListField"] == null)
      {
        modèle.SimpleChoiceListField = "";
      }
      if (posted["MultipleChoiceListField"] == null)
      {
        modèle.MultipleChoiceListField = new string[] { };
      }
      // 表单显示
      return View("Formulaire2", modèle);
}

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

  • 第 18 行:使用视图 [Formulaire2] 代替视图 [Formulaire];
  • 不再处理未勾选的复选框。现在由方法 [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; }

    // 构造函数
    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(即 HTML5)第 5 版引入的新标签。目前并非所有浏览器都支持该版本。如上所示,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>

视觉

 

生成的 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] 的取值为方法 [Html.EditorFor] 的参数属性名称 [Text]。 属性 [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>

视觉

 

生成的 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] 的取值为方法 [Html.EditorFor] 的参数属性名称 [MultiLineText]。情况总是如此。 我们不再赘述。生成的标签为 <textarea>,这是由于元数据

[DataType(DataType.MultilineText)]

明确指出该属性为多行文本。

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

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>

视觉

 

生成的 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]。 属性 [data-val]、[data-val-number] 和 [data-val-required] 是 HTML5 无法识别的属性。它们被一个用于客户端数据验证的 JavaScript 框架所使用;
  • 方法 [Html.DisplayFor] 生成了第 4 行中的文本,即该属性的值。

验证

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

输入一个错误的数字并进行验证:

 

如上所示,验证在客户端进行。在错误得到纠正之前,表单不会被提交。

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

在上文的 [1] 中,[Action10Post] 报告了错误。 大家可能还记得,此前在需要验证的属性上使用 [Required] 属性时(参见第 69 页),即属性 [Number],曾出现过这种行为。而在此处,我们无需进行此操作。

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>

视觉

 

生成的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>

视觉

 

生成的 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 浏览器将其作为类型为 [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>

视觉

 

生成的 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 浏览器能够识别该类型,并支持通过日历输入日期。 此外,输入的日期以 [jj/mm/aaaa] 格式呈现,即 Chrome 会将日期格式调整为浏览器默认的 [locale] 格式。

  • 方法 [Html.DisplayFor] 同样将日期写为 [jj/mm/aaaa] 格式,这同样是由于存在元数据 [Date] 所致。

验证

客户端报告了无效日期 [1],导致表单无法将 POST 提交至服务器。

客户端未报告缺少日期,但服务器端报告了此问题 [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>

视觉

 

生成的 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]。

验证

从技术上讲,无法输入无效的时间。服务器端会提示时间缺失:

 

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>

视觉

 

生成的 HTML


      <tr>
        <td><label for="HiddenInput">HiddenInput</label></td>
        <td>cach&#233;<input id="HiddenInput" name="HiddenInput" type="hidden" value="隐藏" /></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>

图片

 

生成的 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>

视觉

 

生成的 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>

视觉

 

生成的 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:

未输入内容不会引发任何错误。

5.8.13. 属性 [Password]

定义


    [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>

视觉

 

生成的 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>

视觉

 

生成的 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>

视觉

 

生成的 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]。 方法 [Html.DisplayFor] 生成了第 4 行。此处无法看出元数据的作用:

[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; }

    // 验证
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      // 日期 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 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] 用于显示表单:


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

操作 [Action11Post] 用于重新显示包含可能输入错误的表单:


    // 操作11-POST
    [HttpPost]
    public ViewResult Action11Post(ViewModel11 modèle)
    {
      return View("Action11Get", modèle);
}
  • 第 3 行:创建模板 [ViewModel11],并使用提交的值对其进行初始化。此时可能会发生错误。模板中的每个错误属性 P 都会关联一条错误消息。表单的 [Html.ValidationMessageFor] 方法正是用于获取该错误消息的。

以下是一个执行示例:

以下是另一个示例:

 

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


    // 验证
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      // 日期 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" }));
      }
      // 正则表达式1
      try
      {
        DateTime.ParseExact(Regexp1, "dd/MM/yyyy", CultureInfo.CreateSpecificCulture("fr-FR"));
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Regexp1" }));
      }

      // 返回错误列表
      return résultats;
}

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

 

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] 显示:


    // 操作12-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] 并未发生。 但点击 [Valider] 按钮触发了所有客户端验证,并出现了新的错误信息。

我们以首次录入生成的HTML为例:


        <tr>
          <td><label for="Chaine1">Cha&#238;至少四个字符</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,这些属性将被直接忽略,因此不会进行客户端验证。 其工作原理与前例相同。因此,该技术被称为 [unobtrusive]。

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() {
      // 获取文档表单
      var form = document.forms[0];
      // 提交
      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(文本, 操作, 控制器)

第 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]。

欢迎读者亲自测试此示例。