5. 用户界面管理
5.1. Introduction
在Web的客户端-服务器关系中,客户端会以参数字符串的形式向服务器传输信息。在之前的示例中,我们通常通过请求[http://localhost/appli? param1=val1¶m2=val2&...]格式的URL来手动构建该字符串。 实际上,客户端传输给服务器的信息源自用户填写的表单。本章将探讨这些表单的构建方法。此外,我们将介绍 WebMatrix 工具,该工具将帮助我们设计用户界面。该工具的安装方法详见附录。
5.2. HTML 语言
5.2.1. 一个示例
请看以下使用 [Web Matrix] 创建的示例,其中包含:
- 一个表格
- 一张图片
- 一个链接

运行 [WebMatrix],然后选择 [File/New File] 选项:

我们选择创建一个名为 HTML 的页面。 上述信息将生成一个名为 [d:\data\devel\aspnet\chap4\exemple1\exemple1.htm] 的文件。[WebMatrix] 提供了两种编辑该文件的方式:[Design] 模式和 [HTML] 模式:

[Design] 模式允许使用 [WebMatrix] 提供的 HTML 组件面板:

要插入该组件库中的元素,只需双击该元素,然后将其放置在 [Design] 窗口中。[HTML] 模式允许使用文本编辑器构建 HTML 文档。 为此,必须了解HTML语言的语法。在[HTML]选项卡中,已生成一个文档骨架:

对于不熟悉HTML语言的用户而言,[HTML]窗口非常实用。 用户可以在 [Design] 窗口中编写文档,并在 [HTML] 窗口中检查生成的 HTML 代码。 通过这种方式,用户将逐步掌握 HTML,并很快能够仅使用文本编辑器进行操作,而无需依赖 [Design] 模式。 现在我们将演示如何构建本节开头展示的 HTML 文档。我们在 [Design] 窗口中进行操作。首先,我们直接输入第一行文本:

- 我们在组件面板中添加 [Horizontal Rule] 组件:

- 接着添加组件 [Table]:

- 我们将光标置于表格的第三行,使用 [HTML/Edit Table/Delete Table Row] 选项将其删除。然后在每个单元格中输入预期文本:

- 我们将光标置于表格中的任意一个单元格,并查看其属性。属性窗口显示在工作区的右下角:

- 该单元格由标签 HTML <TD> 表示。因此,我们看到的是标签 <TD> 的属性。我们关注的是表格,它是一个包含该单元格的对象。 我们点击上方的下拉列表(点击查看父级 HTML 元素),选择对象 <TABLE>:

- 对象 <TABLE> 具有一个属性 [border],用于设置表格单元格周围边框的宽度。在此,我们采用 border=1。

- 现在我们编辑单元格 (1,2) 中 <TD> 对象的属性,将其 align 设为 Center,width 设为 200(像素)。 文本将在单元格中居中显示(align=center),且该单元格宽度为 200 像素。要查看修改效果,有时可能需要先选中 [HTML] 选项卡,然后切回 [Design] 选项卡:

- 现在,我们在图片前放置文本:

- 然后双击调色板中的 [image] 组件插入图片:

- 选中图片以编辑其属性:

- 在 [src] 属性中,输入包含图片的文件名,此处为 [univ01.gif]:

- 我们输入链接前的文本:

- 我们将文本 [ici] 设置为指向 URL [http://istia.univ-angers.fr] 的链接。为此,我们先选中该文本,然后选择选项 [HTML/Insert Hyperlink]:

- 我们将该文本转换为以下结果:

- 我们已经接近目标了。让我们查看在 [HTML] 选项卡中生成的 HTML 代码:
<html>
<head>
</head>
<body>
<p>
Le langage HTML - 1
</p>
<hr />
<table border="1">
<tbody>
<tr>
<td>
cellule(1,1)</td>
<td align="middle" width="200">
cellule(1,2)</td>
<td>
cellule(1,3)</td>
</tr>
<tr>
<td>
cellule(2,1)</td>
<td>
cellule(2,2)</td>
<td>
cellule(2,3)</td>
</tr>
</tbody>
</table>
<p>
Une image <img src="univ01.gif" />
</p>
<p>
Le site de l'istia <a href="http://istia.univ-angers.fr">ici</a>
</p>
</body>
</html>
我们还有一些细节需要处理。首先,我们希望为文档添加标题。[WebMatrix] 模式下无法实现这一操作。 在 [HTML] 选项卡中,我们将 <head>..</head> 这段代码替换为以下内容:
此外,我们希望将文本 [Le langage HTML - 1] 的字体放大。序列 <Hi>文本</Hi> 可用于设置文本大小,其中 i 的取值范围为 1 到 6,从最大到最小。在此,我们将选用 H2。该序列
将变为:
窗口 [Design] 反映了我们的更改:

接下来,我们只需使用选项 [View/Start] 或 [F5] 进行测试。[WebMatrix] 需要一些信息来启动 Web 服务器 [Cassini]:

我们可以接受默认值。[Cassini] 服务器已启动,我们的页面在浏览器中显示:

出于好奇,我们可以查看 [Cassini] 是使用哪些参数启动的:

我们已介绍了使用 [WebMatrix] 构建 HTML 页面的基础知识。欢迎读者尝试构建其他 HTML 页面,并每次检查生成的 HTML 代码。 随着练习的深入,您将能够不借助 [Design] 模式独立构建页面。HTML 文档的一般格式如下:
整个文档由 <html>...</html> 标签包围。它由两部分组成:
<head>...</head>:这是文档中不可显示的部分。它向将要显示文档的浏览器提供信息。其中通常包含 <title>...</title> 标签,用于设定浏览器标题栏中显示的文本。此外还可能包含其他标签,特别是定义文档关键词的标签,这些关键词随后会被搜索引擎使用。 该部分还可能包含脚本,通常采用JavaScript或VBScript编写,并将由浏览器执行。
<body 属性>...</body>:这是浏览器将显示的部分。该部分包含的标签会向浏览器指示文档的“预期”视觉呈现形式。每个浏览器都会以自己的方式解释这些标签。 因此,同一网页文档在不同浏览器中的显示效果可能存在差异。这通常是网页设计师面临的难题之一。
前一个文档的代码如下:
<html>
<head>
<title>HTML1</title>
</head>
<body>
<h2>Le langage HTML - 1
</h2>
<hr />
<table border="1">
<tbody>
<tr>
<td>
cellule(1,1)</td>
<td align="middle" width="200">
cellule(1,2)</td>
<td>
cellule(1,3)</td>
</tr>
<tr>
<td>
cellule(2,1)</td>
<td>
cellule(2,2)</td>
<td>
cellule(2,3)</td>
</tr>
</tbody>
</table>
<p>
Une image <img src="univ01.gif" />
</p>
<p>
Le site de l'istia <a href="http://istia.univ-angers.fr">ici</a>
</p>
</body>
</html>
元素 | 标签与示例 HTML |
<title>HTML1</title> HTML1 将显示在显示该文档的浏览器标题栏中 | |
<hr>:显示一条水平线 | |
<table 属性>....</table>:用于定义表格 <tr 属性>...</tr>:用于定义一行 <td 属性>...</td>:用于定义单元格 示例: <table border="1">...</table>:border 属性定义表格边框的粗细 <td align="center" width="200">单元格(1,2)</td>:定义一个内容为 cellule(1,2) 的单元格。该内容将水平居中(align="center")。该单元格的宽度为 200 像素(width="200") | |
<img src="univ01.gif" />:定义了一张图片,其源文件为Web服务器上的univ01.gif(src="univ01.gif")。 该链接位于通过 URL 生成的网页文档中 http://localhost/exemple1.htm。 因此,浏览器将请求 URL http://localhost/univ01.gif 以获取此处引用的图片。 | |
<a href="http://istia.univ-angers.fr">此处</a>:使文本 ici 作为指向 URL http://istia.univ-angers.fr 的链接。 |
从这个简单的例子中可以看出,为了构建完整的文档,浏览器必须向服务器发出两次请求:
http://localhost/exemple1.htm 以获取文档
http://localhost/univ01.gif 用于获取图片 univ01.gif
5.2.2. 表单的构建
HTML表单旨在向用户展示一个信息输入页面,其形式类似于Windows系统中的输入表单。该输入表单将以HTML文档的形式发送至浏览器。 浏览器将表单呈现给用户,用户填写后通过相应按钮提交。随后,浏览器将所填入的值传输至服务器进行处理。需要注意的是,它并非将整个表单发送回服务器,而是仅发送已输入的值。以下示例展示了一个同样使用 WebMatrix 创建的 Web 表单:

由 [WebMatrix] 生成的 HTML 代码如下:
<html>
<head>
<title>Formulaire</title>
<script language="javascript">
function effacer(){
alert("Vous avez cliqué sur le bouton [Effacer]");
}
</script>
</head>
<body>
<p>
Gestion d'un formulaire
</p>
<hr />
<form name="formulaire" method="post">
<table border="1">
<tr>
<td>
Etes-vous marié(e)</td>
<td>
<p align="center">
<input type="radio" value="oui" name="rdMarie" />Oui
<input type="radio" checked value="non" name="rdMarie" />Non
</p>
</td>
</tr>
<tr>
<td>
Cases à cocher
</td>
<td>
<p align="center">
<input type="checkbox" value="un" name="C1" />1
<input type="checkbox" checked value="deux" name="C2" />2
<input type="checkbox" value="trois" name="C3" />3
</p>
</td>
</tr>
<tr>
<td>
Champ de saisie</td>
<td>
<p align="center">
<input type="text" maxlength="30" value="qqs mots" name="txtSaisie" />
</p>
</td>
</tr>
<tr>
<td>
Mot de passe</td>
<td>
<p align="center">
<input type="password" maxlength="12" size="12" value="unMotDePasse" name="txtMdp" />
</p>
</td>
</tr>
<tr>
<td>
Boîte de saisie</td>
<td>
<p align="center">
</p>
<textarea name="areaSaisie">ligne1
ligne2</textarea>
</td>
</tr>
<tr>
<td>
ComboBox</td>
<td>
<p align="center">
</p>
<select name="cmbValeurs">
<option value="1">choix1</option>
<option value="2" selected>choix2</option>
<option value="3">choix3</option>
</select>
</td>
</tr>
<tr>
<td>
Liste à choix simple</td>
<td>
<p align="center">
</p>
<select size="3" name="lstSimple">
<option value="1" selected>liste1</option>
<option value="2">liste2</option>
<option value="3">liste3</option>
<option value="4">liste4</option>
<option value="5">liste5</option>
</select>
</td>
</tr>
<tr>
<td>
Liste à choix multiple</td>
<td>
<p align="center">
</p>
<select multiple size="3" name="lstMultiple">
<option value="1" selected>multiple1</option>
<option value="2">multiple2</option>
<option value="3" selected>multiple3</option>
<option value="4">multiple4</option>
<option value="5">multiple5</option>
</select>
</td>
</tr>
<tr>
<td>
Bouton simple</td>
<td>
<p align="center">
<input onclick="effacer()" type="button" value="Effacer" name="btnEffacer" />
</p>
</td>
</tr>
<tr>
<td>
Bouton submit</td>
<td>
<p align="center">
<input type="submit" value="Envoyer" name="btnEnvoyer" />
</p>
</td>
</tr>
<tr>
<td>
Bouton reset</td>
<td>
<p align="center">
<input type="reset" value="Rétablir" name="btnRetablir" runat="server" />
</p>
</td>
</tr>
</table>
<input type="hidden" name="secret" value="uneValeur" />
</form>
</body>
</html>
关联项“视觉检查”与“HTML”标签的对应关系如下:
检查 | HTML 标签 |
<form name="formulaire" method="post"> | |
<input type="text" maxlength="30" value="几个词" name="txtSaisie" /> | |
<input type="password" maxlength="12" size="12" value="unMotDePasse" name="txtMdp" /> | |
<textarea name="areaSaisie">行1 第2行</textarea> | |
<input type="radio" value="是" name="rdMarie" />是 <input type="radio" checked value="否" name="rdMarie" />否 | |
<input type="checkbox" value="一" name="C1" />1 <input type="checkbox" checked value="二" name="C2" />2 <input type="checkbox" value="三" name="C3" />3 | |
<select name="cmbValeurs"> <option value="1">选项1</option> <option value="2" selected>选项2</option> <option value="3">选项3</option> </select> | |
<select size="3" name="lstSimple"> <option value="1" selected>列表1</option> <option value="2">列表2</option> <option value="3">列表3</option> <option value="4">列表4</option> <option value="5">列表5</option> </select> | |
<select multiple size="3" name="lstMultiple"> <option value="1" selected>多选1</option> <option value="2">多选2</option> <option value="3" selected>multiple3</option> <option value="4">multiple4</option> <option value="5">multiple5</option> </select> | |
<input type="hidden" name="secret" value="uneValeur" /> | |
<input type="submit" value="发送" name="btnEnvoyer" /> | |
<input type="reset" value="重置" name="btnRetablir" runat="server" /> | |
<input onclick="effacer()" type="button" value="清除" name="btnEffacer" /> |
让我们来回顾一下这些不同的控件。
5.2.2.1. 表单
<form name="formulaire" method="post"> |
<form name="..." method="..." action="...">...</form> | |
name="frmexemple":表单名称 method="...":浏览器用于将表单中收集的值发送至Web服务器的方法 action="...":URL,表单中收集的值将发送至此处。 网页表单由 <form>...</form> 标签包围。表单可以有一个名称(name="xx")。表单中所有控件均适用此规则。如果网页文档包含需要引用表单元素的脚本,该名称将非常有用。 表单的目的是收集用户通过键盘/鼠标输入的信息,并将这些信息发送至某个Web服务器上的URL。具体是哪个?即action="URL"属性中引用的那个。 如果缺少该属性,信息将发送至包含该表单的文档所在的URL。 Web客户端如何将信息(即表单中的内容)传递给Web服务器?我们已详细探讨过。它可以使用两种不同的方法,即POST和GET。 method="méthode" 属性(其 method 属性值为 GET 或 POST) 会指示浏览器使用该方法,将表单中收集的信息发送至由 action="URL" 属性指定的 URL。 当未指定 action="method" 属性时,默认采用 action="GET" 方法。 |
5.2.2.2. 输入字段

<input type="text" maxlength="30" value="几个字" name="txtSaisie" /> <input type="password" maxlength="12" size="12" value="unMotDePasse" name="txtMdp" /> |
<input type="..." name="..." size=".." value=".."> input 标签适用于各种控件。正是 type 这个属性,使得这些不同的控件能够相互区分。 | |
type="text":指定这是一个文本输入框 type="password":输入框中的字符将显示为星号(*)。这是它与普通输入框的唯一区别。此类控件适用于密码输入。 size="12":字段中显示的字符数——不限制实际输入的字符数量 maxlength="30":将最大字符数限制为30——由浏览器负责执行此属性 name="txtSaisie":控件名称 value="几句话":将在输入框中显示的文本。 |
5.2.2.3. 多行输入框

<textarea name="areaSaisie">第1行 第2行</textarea> |
<textarea ...>文本</textarea> 显示一个多行输入框,初始内容为文本 | |
rows="2":行数 cols="'20":列数 name="areaSaisie":控件名称 |
5.2.2.4. 单选按钮
![]()
<input type="radio" value="是" name="rdMarie" />是 <input type="radio" checked value="否" name="rdMarie" />否 |
<input type="radio" attribut2="valeur2" ..../>文本 显示一个单选按钮,旁边带有文本。 | |
name="rdMarie":控件名称。名称相同的单选按钮构成互斥的按钮组:只能选中其中一个。 value="值":赋予单选按钮的值。请勿将此值与单选按钮旁显示的文本混淆,后者仅用于显示。 checked:如果存在此关键字,单选按钮将被选中;否则则未被选中。 |
5.2.2.5. 复选框
<input type="checkbox" value="一" name="C1" />1 <input type="checkbox" checked value="二" name="C2" />2 <input type="checkbox" value="三" name="C3" />3 |
![]()
<input type="checkbox" attribut2="value2" ..../>文本 显示一个复选框,旁边带有文本。 | |
name="C1":控件名称。复选框可以具有相同的名称,也可以不具有相同的名称。具有相同名称的复选框构成一组关联的复选框。 value="值":分配给复选框的值。请勿将此值与单选按钮旁显示的文本混淆,后者仅用于显示。 checked:如果存在此关键字,单选按钮处于选中状态;否则未选中。 |
5.2.2.6. 下拉列表(组合框)
<select name="cmbValeurs"> <option value="1">选项1</option> <option value="2" selected>选项2</option> <option value="3">选项3</option> </select> |
![]()
<select size=".." name=".."> <option [selected="selected"] [value="valeur"]>文本</option> ... </select> 将位于 <option>...</option> 标签之间的文本显示为列表 | |
name="cmbValeurs":控件名称。 size="1":可见列表项的数量。size="1"将列表转换为下拉列表框。如果缺少[size]属性,则此为默认值。 selected="selected":如果列表项包含此关键字,该项在列表中将显示为已选中。在上文示例中,当下拉列表首次显示时,列表项 choix2 将显示为已选中的项。 value="值":指定当该项被选中时发送给服务器的值。如果缺少此属性,则发送给服务器的将是该选项关联的文本。 |
5.2.2.7. 单选列表
<select size="3" name="lstSimple"> <option value="1" selected>列表1</option> <option value="2">列表2</option> <option value="3">列表3</option> <option value="4">列表4</option> <option value="5">列表5</option> </select> |

<select size=".." name=".."> <option [selected] [value="valeur"]>...</option> ... </select> 在列表中显示位于 <option>...</option> 标签之间的文本 | |
之间的文本,与仅显示一个项的下拉列表相同。该控件与前一个下拉列表的唯一区别在于其 size>1 属性。 |
5.2.2.8. 多选列表
<select multiple size="3" name="lstMultiple"> <option value="1" selected>multiple1</option> <option value="2">multiple2</option> <option value="3" selected>multiple3</option> <option value="4">multiple4</option> <option value="5">multiple5</option> </select> |

<select size=".." name=".." multiple> <option [selected] ] [value="valeur"]>...</option> ... </select> 在列表中显示位于 <option>...</option> 标签之间的文本 | |
multiple:允许在列表中选择多个项目。在上例中,liste1 和 liste3 这两个项目均被选中。 |
5.2.2.9. button 类型按钮
<input onclick="effacer()" type="button" value="清除" name="btnEffacer" /> |
![]()
<input type="button" value="..." name="..." onclick="effacer()" ....> | |
type="button":定义一个按钮控件。还有另外两种类型的按钮,即 submit 和 reset。 value="清除":按钮上显示的文本 onclick="函数()":用于定义用户点击按钮时要执行的函数。该函数属于显示的网页文档中定义的脚本。上述语法是 javascript 语法。如果脚本使用 vbscript 编写,则应写为 onclick="函数",不带括号。 若需向函数传递参数,语法保持不变:onclick="函数(val1, val2,...)" 在本例中,点击按钮 Effacer 将调用以下 JavaScript 函数 effacer: 函数 effacer 显示一条消息: ![]() |
5.2.2.10. 提交按钮
<input type="submit" value="发送" name="btnEnvoyer" /> |
![]()
<input type="submit" value="发送" name="btnEnvoyer"> | |
type="submit":将该按钮定义为将表单数据发送至Web服务器的提交按钮。当用户点击该按钮时,浏览器将根据<form>标签中method属性定义的方法,将表单数据发送至该标签action属性中定义的URL。 value="提交":按钮上显示的文本 |
5.2.2.11. 重置按钮
<input type="reset" value="重置" name="btnRetablir" runat="server" /> |
![]()
<input type="reset" value="重置" name=" btnRetablir "> | |
type="reset":将该按钮定义为表单重置按钮。当用户点击此按钮时,浏览器将把表单恢复到初始接收时的状态。 value="重置":按钮上显示的文本 |
5.2.2.12. 隐藏字段
<input type="hidden" name="secret" value="uneValeur" /> |
<input type="hidden" name="..." value="..."> | |
type="hidden":指定这是一个隐藏字段。隐藏字段是表单的一部分,但不会显示给用户。不过,如果用户要求浏览器显示源代码,就会看到 <input type="hidden" value="..."> 标签,从而看到隐藏字段的值。 value="uneValeur":隐藏字段的值。 隐藏字段有何作用?它可让 Web 服务器在处理客户端的请求时保存信息。以一个在线购物应用为例。 客户在目录的首个页面上购买了第一件商品 art1,数量为 q1,随后跳转到目录的另一页面。 为了记住客户已购买了 q1 和 art1 这两件商品,服务器可以将这两条信息放入新页面网页表单中的隐藏字段中。 在该新页面上,客户购买了 q2 和 art2 两件商品。当该第二个表单的数据被提交至服务器时,服务器不仅会收到 (q2,art2),还会收到 (q1,art1)——后者作为用户无法修改的隐藏字段,同样属于该表单的一部分。 随后,Web服务器将把信息(q1,art1)和(q2,art2)放入一个新的隐藏字段中,并发送新的目录页面。以此类推。 |
5.3. 浏览器将表单值发送至Web服务器
通过上一章,我们已经知道客户端是如何向服务器传输信息的。客户端通常通过以下方式进行:
- 一个包含 URL 及参数的请求,例如:HTTP GET url?param1=va1¶m2=val2&....
- 一个 HTT POST URL 请求,后跟一个包含参数字符串 param1=va1¶m2=val2&.... 的文档。
浏览器将根据 [form] 标签的 [method] 属性是 GET 还是 POST,采用这两种方法中的一种。接下来我们将对此进行说明。 前面研究的页面是一个静态页面。为了获取请求该文档的浏览器发送的 HTTP 头部信息,我们将它转换为面向 .NET(IIS 或 Cassini)Web 服务器的动态页面。 之前的静态代码被放置在一个名为 [formulaire_get.aspx] 的文件中,其内容如下:
<%@ Page src="formulaire_get.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="formulaire_get" %>
<html>
<head>
<title>Formulaire</title>
<script language="javascript">
function effacer(){
alert("Vous avez cliqué sur le bouton [Effacer]");
}
</script>
</head>
<body>
.....
</body>
</html>
上述介绍页面与控制器 [formulaire_get.aspx.vb] 相关:
Public Class formulaire_get
Inherits System.Web.UI.Page
Private Sub Page_Init(ByVal Sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
' 将当前请求保存到页面文件夹中的 request.txt
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
End Class
每次调用文档 [formulaire_get.aspx] 时,客户端的请求都会通过过程 Page_Init 保存到文件 [request.txt] 中。我们之前已经遇到过这种工作模式,因此不再对该控制器进行详细说明。
5.3.1. 方法 GET
让我们进行一次初步测试,在文档的 HTML 代码中,FORM 标签定义如下:
<form name="formulaire" method="get">
我们将文件 [formulaire_get.aspx] 和 [formulaire_get.aspx.vb] 放置在 <application-path> 文件夹中,并使用参数 (<application-path>,/form2) 启动 Cassini 服务器。 访问网址 http://localhost/form2/formulaire_get.aspx:

浏览器刚刚发送了一个请求,我们知道该请求已被记录在文件 [request.txt] 中。让我们查看该文件的内容:
GET /form2/formulaire_get.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
浏览器使用请求 HTTP GET 来请求 URL [http://localhost/form2/formulaire_get.aspx]。当 URL 由用户提供时,情况总是如此。我们按以下方式填写表单:

我们使用上方的按钮 [Envoyer]。其代码 HTML 如下:
点击类型为 [Submit] 的按钮时, 浏览器会将表单参数(<form>标签)发送至<form action="URL">标签中[action]属性所指定的URL(若该属性存在)。 如果该属性不存在,表单参数将发送至生成该表单的 URL。本例即属此情况。 因此,[Envoyer]按钮应触发浏览器向URL和[http://localhost/form2/formulaire_get.aspx]发送请求,并传递表单参数。 由于页面 [formulaire_get.aspx] 会记录收到的请求,我们应该能知道客户端是如何传递这些参数的。让我们试一试。我们点击按钮 [Envoyer]。随后收到浏览器的以下响应:

这是初始页面,但可以注意到浏览器中的 [Adresse] 字段已发生变化。它变成了如下内容:
http://localhost/form2/formulaire_get.aspx?rdMarie=是&C1=一&C2=二&txtSaisie=编程+asp.net&txtMdp=unMotDePasse&areaSaisie=基础+知识+0D%0A网页编程%0D%0A&cmbValeurs=3&lstSimple=1&lstMultiple=2&lstMultiple=4&btnEnvoyer=发送&secret=uneValeur
我们可以看到,表单中的选项已反映在 URL 中。现在让我们查看存储了客户端请求的 [request.txt] 文件的内容:
GET /form2/formulaire_get.aspx?rdMarie=oui&C1=un&C2=deux&txtSaisie=programmation+asp.net&txtMdp=ceciestsecre&areaSaisie=les+bases+de+la%0D%0Aprogrammation+web%0D%0A&cmbValeurs=3&lstSimple=1&lstMultiple=2&lstMultiple=4&btnEnvoyer=Envoyer&secret=uneValeur HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form2/formulaire_get.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
这里出现了一个名为 HTTP 的请求,与浏览器最初在未传递任何参数的情况下请求文档时发出的请求非常相似。两者有两处不同:
表单参数以 ?param1=val1¶m2=val2&... 的形式附加在文档 URL 之后 | |
客户通过此标头 HTTP 指明了其在发起请求时正在显示的文档的 URL |
让我们进一步分析参数是如何传递到 GET 命令中的:URL?param1=value1¶m2=value2&... HTTP/1.1,其中 parami 是 Web 表单控件的名称,valeuri 是与其关联的值。下面是一个三列表格:
- 第 1 列:引用示例中控件 HTML 的定义
- 第 2 列:显示该控件在浏览器中的显示效果
- 第 3 列:显示浏览器为第 1 列中的控件发送给服务器的值,该值以示例中 GET 请求中的形式呈现
控件 HTML | 提交前的显示效果 | 返回的值 |
<input type="radio" value="是" name="rdMarie" />是 <input type="radio" checked value="否" name="rdMarie" />否 | rdMarie=是 - 用户勾选的按钮的 value 属性值 用户选中的单选按钮的属性值。 | |
<input type="checkbox" value="un" name="C1" />1 <input type="checkbox" checked value="deux" name="C2" />2 <input type="checkbox" value="三" name="C3" />3 | C1=一 C2=二 - 用户勾选的复选框的属性值 用户勾选的复选框 | |
<input type="text" maxlength="30" value="几个 词" name="txtSaisie" /> | txtSaisie=programmation+asp.net - 用户在输入字段中输入的文本 。空格已被 为 + 号 | |
<input type="password" maxlength="12" size="12" value="unMotDePasse" name="txtMdp" /> | txtMdp=这是秘密 - 用户在输入框中输入的文本 。实际输入的文本是 “ceciestsecret”。最后一个字符 丢失,因为 maxlength="12" 限制了 限制为12个字符。 | |
<textarea name="areaSaisie"> 行1 行2</textarea> | ![]() | areaSaisie=les+bases+de+ 0D%0A 网页编程 - 用户在输入字段中输入的文本 。%OD%OA 是 。空格已被 替换为 + 符号 |
<select name="cmbValeurs"> <option value="1">选项1 </option> <option value="2" selected>选项2 </option> <option value="3">选项3 </option> </select> | cmbValeurs=3 - 用户在 单选列表中选择的值 | |
<select size="3" name="lstSimple"> <option value="1" selected>列表1</option> <option value="2">列表2 </option> <option value="3">列表3 </option> <option value="4">列表4 </option> <option value="5">列表5 </option> </select> | ![]() | lstSimple=3 - 用户在 单选列表中选择的值 |
<select multiple size="3" name="lstMultiple"> <option value="1" selected>multiple1 </option> <option value="2">multiple2 </option> <option value="3" selected>multiple3 </option> <option value="4">多重4 </option> <option value="5">multiple5 </option> </select> | ![]() | lstMultiple=2 lstMultiple=4 - 用户在 多选列表中 |
<input type="submit" value="发送" name="btnEnvoyer" /> | btnEnvoyer=发送 - 用于 用于将表单数据 的按钮名称和属性 | |
<input type="hidden" name="secret" value="一个值" /> | secret=uneValeur - 隐藏字段的 value 属性 |
人们可能会好奇,服务器对传入的参数做了什么处理。实际上什么也没做。在接收到命令时
GET /form2/formulaire_get.aspx?rdMarie=oui&C1=un&C2=deux&txtSaisie=programmation+asp.net&txtMdp=ceciestsecre&areaSaisie=les+bases+de+la%0D%0Aprogrammation+web%0D%0A&cmbValeurs=3&lstSimple=1&lstMultiple=2&lstMultiple=4&btnEnvoyer=Envoyer&secret=uneValeur HTTP/1.1
Web 服务器已将参数传递给 URL,并将其应用于文档 [http://localhost/form2/formulaire_get.aspx]、c.a.d,以及我们最初构建的文档。 我们并未编写任何代码来获取和处理客户端发送的参数。因此,一切都仿佛客户端的请求仅仅是:
正因如此,当我们点击 [Envoyer] 按钮时,得到的页面与最初调用 URL 和 [http://localhost/form2/formulaire_get.aspx](未带参数)时完全相同。
5.3.2. 方法 POST
文档 HTML 现已配置为让浏览器使用 POST 方法将表单值发送至 Web 服务器。 为此,我们将文件 [formulaire_get.aspx] 复制为 [formulaire_post.aspx],并仅修改 [formulaire_post.aspx] 中的 <form> 标签:
<%@ Page src="formulaire_get.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="formulaire_get" %>
<html>
<head>
...
</head>
<body>
<p>
Gestion d'un formulaire
</p>
<hr />
<form name="formulaire" method="post">
<table border="1">
无需更换控制器 [formulaire_get.aspx.vb],因此保持原状。 我们通过 URL [http://localhost/form2/formulaire_post.aspx] 请求新文档,按照 GET 方法填写表单,并使用 [Envoyer] 按钮将参数提交至服务器。 我们从服务器获取到以下响应页面:

因此,我们得到的结果与方法 GET、c.a.d 相同,即初始页面。 值得注意的是:在浏览器的 [Adresse] 字段中,未显示传输的参数。现在,让我们查看客户端发送并存储在 [request.txt] 文件中的请求:
POST /form2/formulaire_post.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 222
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form2/formulaire_post.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
rdMarie=oui&C1=un&C2=deux&txtSaisie=programmation+asp.net&txtMdp=ceciestsecre&areaSaisie=les+bases+de+la%0D%0Aprogrammation+web%0D%0A&cmbValeurs=3&lstSimple=3&lstMultiple=2&lstMultiple=4&btnEnvoyer=Envoyer&secret=uneValeur
客户查询 HTTP 中出现了新内容:
查询 GET 已被查询 POST 取代。 这些参数不再出现在查询的第一行中。可以看出,它们现在被放置在查询 HTTP 之后,其前有一行空行。它们的编码与在查询 GET 中的编码相同。 | |
“发送”的字符数,c.a.d。这是 Web 服务器在收到 HTTP 头部后,为获取客户端发送的文档所需读取的字符数。此处的文档即为表单值的列表。 | |
指定客户端在发送完 HTTP 头部后将发送的文档类型。类型 [application/x-www-form-urlencoded] 表示该文档包含表单值。 |
向 Web 服务器传输数据有两种方法:GET 和 POST。哪种方法更好?我们看到,如果表单值由浏览器通过 GET 方法发送, 浏览器会在其 Adresse 字段中以 URL 的形式显示所请求的 URL?param1=val1¶m2=val2&.... 这既可以视为优点,也可以视为缺点:
- 如果希望允许用户将此带参数的 URL 添加到收藏夹中,则视为优点
- 若不希望用户访问表单中的某些信息(例如隐藏字段),则属于缺点
此后,我们在表单中将几乎完全使用 POST 方法。
5.4. 表单值的服务器端处理
5.4.1. 示例介绍
既然我们已经建立了 HTML 标签 <form method="GET/POST" ...> 与浏览器发送表单值方式之间的关联,我们就知道如何在服务器端获取这些值了。 上章节已经给出了答案:
- 如果 <form> 标签的方法是 GET,则参数值将从集合 [Request.QueryString] 中获取
- 如果 <form> 标签的方法是 POST,则“提交”的参数值将从集合 [Request.Form] 中获取。这里需要补充说明一点。 <form> 标签可通过 [action] 属性指定 GET 或 POST 的目标 URL。 无论使用的是 GET 还是 POST,该 URL 均可采用 action="url?param1=val1¶m2=val2&..." 的形式进行配置。 这些参数将附加到 <form> 和 </form> 标签之间的参数中,并通过 GET 或 POST 传输至服务器。 由于它们属于目标 URL 的一部分,无论处理的是 GET 还是 POST,都将从 [Request.QueryString] 集合中获取。
现在,我们编写一个包含两个视图的 MVC 应用程序:
- 第一个视图即前文所述的表单。我们将它命名为 [vue_formulaire]。
- 第二个视图是一个信息页面,列出了在第一个页面中输入的值。通过一个链接可以返回表单。我们将这个视图命名为 [vue_confirmation]。
视图 [vue_formulaire] 发送给用户,由用户填写并提交。在提交前,它可能如下所示:

用户点击按钮 [Envoyer] 来提交其输入内容。随后,他将收到如下视图 [vue_validation]:

5.4.2. 应用程序控制器
我们在上一章中看到,我们可以将应用程序 MVC 的控制器角色委托给文件 [global.asax],所有客户端请求都将通过该文件进行处理。 我们之前已经介绍过一个按此方式构建的 MVC 应用程序,这里我们将遵循当时介绍的开发模式。[global.asax] 文件内容如下:
该文件仅包含一行代码,引用了位于文件 [global.asax.vb] 中的控制器:
Imports System
Imports System.Web
Imports System.Web.SessionState
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
' 获取待执行的操作
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' 执行该操作
Select Case action
Case "init"
Server.Transfer("formulaire.aspx", False)
Case "validation"
Server.Transfer("validation.aspx", True)
Case Else
Server.Transfer("formulaire.aspx", True)
End Select
End Sub
End Class
该控制器的原理如下:
- 它在目标URL中等待一个包含参数[action]的参数字符串。如果该参数缺失,则将其视为参数字符串中包含[action=init]。
- 仅支持两种操作:
- init:向客户端返回预填充的表单
- validation:向客户端返回确认其输入内容的页面
- 如果操作不属于上述两种情况,则视为参数串中包含 [action=init]。也可以返回一个错误页面。
5.4.3. 处理 init 操作
当控制器处理“init”操作时,必须生成一个预填充表单。这是页面 [formulaire.aspx] 的职责。其代码如下:
<HTML>
<HEAD>
<title>Formulaire</title>
<script language="javascript">
function effacer(){
alert("Vous avez cliqué sur le bouton [Effacer]");
}
function raz(liste){
liste.selectedIndex=-1
}
</script>
</HEAD>
<body>
<p>
Gestion d'un formulaire
</p>
<hr>
<form name="formulaire" method="post" action="?action=validation">
<table border="1">
<tr>
<td>
Etes-vous marié(e)</td>
<td>
<p align="center">
<input type="radio" value="oui" name="rdMarie">Oui <input type="radio" checked value="non" name="rdMarie">Non
</p>
</td>
</tr>
....
<td>
<select size="3" name="lstSimple">
<option value="1" selected>liste1</option>
<option value="2">liste2</option>
<option value="3">liste3</option>
<option value="4">liste4</option>
<option value="5">liste5</option>
</select>
<INPUT type="button" value="Raz" name="btnRazSimple" onclick="raz(lstSimple)">
</td>
....
<td>
<select multiple size="3" name="lstMultiple">
<option value="1" selected>multiple1</option>
<option value="2">multiple2</option>
<option value="3" selected>multiple3</option>
<option value="4">multiple4</option>
<option value="5">multiple5</option>
</select>
<INPUT type="button" value="Raz" name="btnRazMultiple" onclick="raz(lstMultiple)">
</td>
...
</table>
<input type="hidden" name="secret" value="uneValeur">
</form>
</body>
</HTML>
我们发现,与之前分析的表单相比,代码HTML仅存在一些细微差异。标签<form>的代码已发生变更:
<form name="formulaire" method="post" action="?action=validation">
- post:当用户点击类型为 [submit] 的按钮时,用户输入并发送至服务器的值将通过 HTTP 方法进行处理POST
- action:action="url" 语法用于指定表单值应发送到的 URL。该 URL 可以包含形式为 param1=val1¶m2=val2&... 的参数字符串 此处正是如此,我们传递参数 [action=validation] 以告知控制器应执行的操作。需要注意的是,该参数串前未带网址。因此,浏览器将把表单参数发送至提供该表单的地址。 在上述示例中,该地址为 [http://localhost/mvcform1]。因此,浏览器将向服务器 [localhost] 发送请求 [POST /mvcform1?action=validation]。
已添加按钮,以便用户取消选择列表 [lstSimple] 和 [lstMultiple] 中的项目:

这将生成以下代码 HTML:
<td>
<select size="3" name="lstSimple">
<option value="1" selected>liste1</option>
<option value="2">liste2</option>
<option value="3">liste3</option>
<option value="4">liste4</option>
<option value="5">liste5</option>
</select>
<INPUT type="button" value="Raz" name="btnRazSimple" onclick="raz(lstSimple)">
</td>
....
<td>
<select multiple size="3" name="lstMultiple">
<option value="1" selected>multiple1</option>
<option value="2">multiple2</option>
<option value="3" selected>multiple3</option>
<option value="4">multiple4</option>
<option value="5">multiple5</option>
</select>
<INPUT type="button" value="Raz" name="btnRazMultiple" onclick="raz(lstMultiple)">
</td>
点击按钮 [Raz] 将取消选中与其关联的列表中的所有项目。 以列表 [lstMultiple] 为例。点击对应的按钮 [Raz],将触发 JavaScript 函数 [raz(lstMultiple)] 的执行。 需要提醒的是,浏览器显示的 HTML 文档中的 JavaScript 代码是由该浏览器本身执行的,而非由服务器执行。JavaScript 是一种功能非常全面的语言,它可以在无需服务器干预的情况下,为网页增添动态效果。[raz] 函数如下:
该函数接收一个参数,该参数是一个代表列表 HTML 的 JavaScript 对象。该对象具有属性与方法。其中一个属性是 [selectedIndex],其值为列表 HTML 中所选第一个选项的编号。 如果未选中任何选项,该属性的值为 -1。反之,为该属性赋值相当于在列表 HTML 中选中一个新项,而将其值设为 -1 则相当于未选中任何项。此处正是如此操作。
最后需要注意的是,呈现代码 [formulaire.aspx] 并未配有控制器 [formulaire.aspx.vb]。实际上,文档 HTML 中没有任何由服务器生成的动态部分,因此无需使用控制器。
5.4.4. 处理“validation”操作
当控制器处理“validation”操作时,它必须生成一个列出用户输入值的页面。[validation.aspx] 页面负责完成这项工作。该页面的视觉呈现如下:

其代码 HTML 如下:
<%@ Page src="validation.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="validation" %>
<HTML>
<HEAD>
<title>validation</title>
</HEAD>
<body>
<P>Valeurs saisies</P>
<HR width="100%" SIZE="1">
<TABLE id="Table1" cellSpacing="1" cellPadding="1" width="300" border="1">
<TR>
<TD width="84">rdMarie</TD>
<TD><% =rdMarie%></TD>
</TR>
<TR>
<TD width="84">C1</TD>
<TD><%=C1%></TD>
</TR>
<TR>
<TD width="84">C2</TD>
<TD><%=C2%></TD>
</TR>
<TR>
<TD width="84">C3</TD>
<TD><%=C3%></TD>
</TR>
<TR>
<TD width="84">txtSaisie</TD>
<TD><%=txtSaisie%></TD>
</TR>
<TR>
<TD width="84">txtMdp</TD>
<TD><%=txtMdp%></TD>
</TR>
<TR>
<TD width="84">areaSaisie</TD>
<TD><%=areaSaisie%></TD>
</TR>
<TR>
<TD width="84">cmbValeurs</TD>
<TD><%=cmbValeurs%></TD>
</TR>
<TR>
<TD width="84">lstSimple</TD>
<TD><%=lstSimple%></TD>
</TR>
<TR>
<TD width="84">lstMultiple</TD>
<TD><%=lstMultiple%></TD>
</TR>
<TR>
<TD width="84">secret</TD>
<TD><%=secret%></TD>
</TR>
</TABLE>
</body>
</HTML>
文档中的动态部分 <%=variable%> 由关联的控制器 [validation.aspx.vb] 计算得出:
Imports System.Text.RegularExpressions
Public Class validation
Inherits System.Web.UI.Page
Protected rdMarie As String
Protected C1 As String
Protected C2 As String
Protected C3 As String
Protected txtSaisie As String
Protected txtMdp As String
Protected areaSaisie As String
Protected cmbValeurs As String
Protected lstSimple As String
Protected lstMultiple As String
Protected secret As String
Protected delimiteur As New Regex("\r\n")
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'获取提交的参数
rdMarie = getValue("rdMarie")
C1 = getValue("C1")
C2 = getValue("C2")
C3 = getValue("C3")
txtSaisie = getValue("txtSaisie")
txtMdp = getValue("txtMdp")
areaSaisie = String.Join(",", delimiteur.Split(getValue("areaSaisie")))
cmbValeurs = getValue("cmbValeurs")
lstSimple = getValue("lstSimple")
lstMultiple = getValue("lstMultiple")
secret = getValue("secret")
End Sub
Private Function getValue(ByVal champ As String) As String
' 获取提交请求中字段 [champ] 的值
' 是什么?
If Request.Form(champ) Is Nothing Then Return ""
' 获取字段的值
Dim valeurs() As String = Request.Form.GetValues(champ)
Dim valeur As String = ""
Dim i As Integer
For i = 0 To valeurs.Length - 1
valeur += "[" + valeurs(i) + "]"
Next
Return valeur
End Function
End Class
要显示的值的计算是在过程 [Form_Load] 中进行的。一个“已提交”字段的值是通过函数 getValue(C) 获得的,其中 C 是字段的名称。该函数的关键点如下:
- 如果 C 不在已发布的参数字符串中,则将空字符串作为 C 的值返回
- 否则,通过 [Request.Form.GetValues(C)] 获取字段 C 的值数组。这些值将按 [val1][val2]... 的形式拼接成字符串[valn],其中 [vali] 是字段 C 的第 i 个值
字段 [areaSaisie] 的处理方式特殊。其值由浏览器以 areaSaisie=行1\r\n行2\r\n... 的形式发送 其中 \r 对应代码 ASCII 13(回车),\n 对应代码 ASCII 10(换行)。 因此,函数 getValue(areaSaisie) 的返回值即为字符串 "行1\r\n行2\r\n..."。该字符串通过方法 [Regex.Split] 按行拆分。 随后得到一个字符串数组 {行1,行2,...}。该数组通过方法 [String.Join] 转换为字符串 "行1,行2,..."。正是这个最终字符串将作为字段 [areaSaisie] 的值显示出来。 此处的目的是展示如何获取类型为 [TextArea] 的字段 HTML 中的各行数据。
5.4.5. 测试
我们将所有文件(global.asax、global.asax.vb、formulaire.aspx、validation.aspx、 validation.aspx.vb)放入 <application-path> 文件夹中。我们使用参数 (<application-path>,/mvcform1) 启动 Cassini 服务器。 我们在<application-path>文件夹中创建一个空的[default.aspx]文件,然后请求URL [http://localhost/mvcform1]。 由于 [/mvcform1] 是文件夹的虚拟路径而非文档路径,因此如果存在 [default.aspx] 文档,Web 服务器将显示该文档。这就是我们创建该文档的原因。 在显示该页面之前,脚本 [global.asax] 将被执行,并最终显示页面 [formulaire.aspx]。这就是为什么文件 [default.aspx] 可以为空。
假设已验证的表单如下:

点击按钮 [Envoyer] 将返回以下页面:

首先请注意响应的 URL:[http://localhost/mvcform1/?action=validation]。这是表单 [formulaire.aspx] 中 <form> 标签的 [action] 属性对应的 URL:
<form name="formulaire" method="post" action="?action=validation">
让我们逐一查看表单中各个字段的值:
字段 | 值 | HTML | 备注 |
rdMarie | 是 | <input type="radio" value="是" name="rdMarie">是 <input type="radio" checked value="否" name="rdMarie">否 | 得到的值是已选中按钮的 [value] 属性 |
C1 | 一个 | <input type="checkbox" value="un" name="C1">1 | 同上 |
C2 | 二 | <input type="checkbox" checked value="二" name="C2">2 | 同上 |
C3 | <input type="checkbox" value="三" name="C3">3 | 该复选框未被用户勾选。因此,其值未被浏览器发送。在代码中,条件 [Request.Form("C3") is Nothing] 因此为真。 | |
txtSaisie | 编程 asp.net | <input type="text" ... name="txtSaisie"> | 得到的值是验证时输入框中的文本 |
txtMdp | mdp | <input type="password" ...name="txtMdp"> | 同上 |
areaSaisie | Web 网页编程 | <textarea name="areaSaisie"> ...</textarea> | 同上 |
cmbValeurs | 3 | <select name="cmbValeurs"> ... <option value="3">选项3</option> </select> | 所得值为所选选项的 [value] 属性 |
lstSimple | 3 | <select size="3" name="lstSimple"> .... <option value="3">列表3</option> ... </select> | 同上 |
lstMultiple | 2 4 | <select multiple size="3" name="lstMultiple"> ... <option value="2">multiple2 </option> ... <option value="4">multiple4 </option> ... </select> | 所得值即为所选选项的[value]属性值 |
secret | uneValeur | <input type="hidden" name="secret" value="uneValeur"> | 得到的值是隐藏字段的 [value] 属性 |
现在我们来看看列表。假设在提交表单时,表单的状态如下:

- 在 comboBox 中选中了选项 [choix1]
- 另外两个列表中均未选中任何选项。我们使用 [Raz] 按钮实现了这一效果。
以下是提交后返回的页面:

当列表中未选中任何值时,浏览器不会为此列表发送参数。 因此,在 [validation.aspx.vb] 的代码中,表达式 [Request.Form("lstSimple")] 和 [Request.Form("lstMultiple")] 等于常量 [Nothing],从而导致了上述显示结果。
5.5. 保持页面的状态
5.5.1. 使用会话保持页面状态
我们将之前的整个应用程序复制到一个新文件夹中。我们在页面 [validation.aspx] 中添加了一个链接,允许用户返回表单:

对页面 [validation.aspx] 进行的修改是添加以下链接:
.....
<TR>
<TD width="84">secret</TD>
<TD><%=secret%></TD>
</TR>
</TABLE>
<P>
<a href="?action=formulaire">Retour au formulaire</a>
</P>
</body>
</HTML>
该链接的 [href] 属性的值为一个带参数的 URL,其中唯一参数为 [action=formulaire],这使服务器能够识别应显示表单在提交时的原始状态。 因此,这与显示预定义表单的 [init] 操作有所不同。带参数的 URL [?action=formulaire] 实际上并没有 URL。因此,该 URL 将与显示验证页面的 URL 相同。 回顾前文,该URL即为应用程序文件夹的路径。因此,请求将通过控制器处理。此时控制器需处理操作 [formulaire]。[global.asax.vb] 的代码修改如下:
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
' 获取待执行的操作
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' 执行该操作
Select Case action
Case "init"
Server.Transfer("formulaire.aspx", False)
Case "validation"
Server.Transfer("validation.aspx", True)
Case "formulaire"
Server.Transfer("formulaire.aspx", True)
Case Else
Server.Transfer("formulaire.aspx", True)
End Select
End Sub
如果操作类型为“表单”,我们只需将请求转发至页面 [formulaire.aspx]。我们知道该页面会显示一个预定义的表单,其中不包含任何可变部分。因此,该页面无法显示表单在提交时所具有的值。 此处旨在说明这一要点。如果应用程序的所有文件都放置在 <application-path> 中,我们将使用参数 (<application-path>,/mvcform2) 启动 Cassini,然后请求 URL [http://localhost/mvcform2]。我们将获得以下页面(部分视图):

我们按以下方式填写表单并提交:

随后将显示以下确认页面:

我们使用上述页面中存在的(但未显示的)链接 [Retour au formulaire]。我们从服务器获得以下响应:

我们注意到:
- 请求的URL确实包含action=formulaire参数,符合预期
- 显示的表单已丢失输入的值。
我们知道为何无法找回已输入的值。如果查看文档 [formulaire.aspx] 的代码,我们会发现一切都是静态的,因此它每次都不得不显示相同的页面。 显然,我们需要将该代码改为动态形式。它必须显示用户已验证的值。但这些值该存储在哪里呢?
让我们回顾一下无状态协议 HTTP 的铁律:
- 请求 URL [http://localhost/mvcform2] —— 收到响应。在请求开始时,客户端与服务器之间建立了一条连接 TCP-IP,并在响应结束时终止。
- 用户输入并提交数据。请求 URL [http://localhost/mvcform2/?action=validation] — 收到响应。双方之间建立并关闭了一条新的连接 TCP-IP。
- 用户使用链接 [Retour au formulaire]。请求 URL [http://localhost/mvcform2/?action=formulaire] —— 收到响应。双方之间再次建立并关闭了新的连接 TCP-IP。
这些请求-响应循环彼此独立,因为每个循环都使用新的连接 TCP-IP。 当客户端-服务器应用程序使用单一连接 TCP-IP 进行一系列交互时,服务器可通过该连接识别客户端。因此,服务器可以存储信息并将其与特定连接关联,从而跟踪交互过程。当交互通过不同连接进行时,服务器无法将客户端与特定连接关联起来。 必须采用其他方法。我们在上一章中介绍了一种方法:会话机制。它允许请求-响应循环将信息存储在一个 [Session] 对象中,该对象可供所有后续循环访问。
现在我们拥有了一个带会话的MVC应用程序,因此无法再像上一章所示那样使用[global.asax]文件作为控制器。 应用程序控制器的角色应由专门为此设计的特定页面承担。在此,该页面为 [main.aspx]。我们将按以下步骤进行:
- 当用户提交数据时(action=validation),控制器 [main.aspx.vb] 会先将数据存储在当前会话中,然后显示验证页面。
- 当需要显示包含已输入值的表单时(action=formulaire),控制器 [main.aspx.vb] 将从会话中检索这些数据并将其放入上下文中,然后显示表单。
5.5.2. 新的应用程序控制器
应用程序控制器由以下两个文件组成:
[main.aspx]
[main.aspx.vb]
Imports System.Collections.Specialized
Imports Microsoft.VisualBasic
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' 获取待执行的操作
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' 执行操作
Select Case action
Case "init"
' 显示预填充表单
Context.Items("formulaire") = initForm()
Server.Transfer("formulaire.aspx", True)
Case "validation"
' 将提交的值保存到会话后,显示确认页面
Session.Item("formulaire") = Request.Form
Server.Transfer("validation.aspx", True)
Case "formulaire"
' 显示从会话中获取值的表单
Context.Items("formulaire") = Session.Item("formulaire")
Server.Transfer("formulaire.aspx", True)
Case Else
' 显示预填充的表单
Context.Items("formulaire") = initForm()
Server.Transfer("formulaire.aspx", True)
End Select
End Sub
Private Function initForm() As NameValueCollection
' 初始化表单
Dim form As New NameValueCollection
form.Set("rdMarie", "non")
form.Set("C2", "deux")
form.Set("txtSaisie", "qqs mots")
form.Set("txtMdp", "ceciestsecret")
form.Set("areasaisie", "ligne1" + ControlChars.CrLf + "ligne2" + ControlChars.CrLf)
form.Set("cmbValeurs", "2")
form.Set("lstSimple", "1")
form.Set("lstMultiple", "1")
form.Add("lstMultiple", "3")
Return form
End Function
End Class
这里保留了之前所学应用控制器结构的精髓,但存在以下区别:
- 控制器的工作在 [Form_Load] 过程内进行
- 对于操作 [validation],[Request.Form] 中表单的值将存储在会话中,并关联键“formulaire”。随后,执行流程将转移至页面 [validation.aspx],该页面将显示这些值。
- 对于其他操作,执行流程在所有情况下都会转至页面 [formulaire.aspx]。 该页面在其上下文中期待一个“表单”键,该键将关联一个类型为 [Request.form]、c.a.d 或 [NameValueCollection] 的对象。该对象必须包含表单字段值的集合。
- 若操作为 [init] 或未识别的操作,则该值集合由函数 [initForm] 任意构建。
- 如果操作是 [formulaire],则该集合即为由操作 [validation] 放入会话中的集合 [Request.Form]
5.5.3. 新表单
由于应用程序不再在表单中显示相同的内容,因此必须动态生成该表单。新页面 [formulaire.aspx] 如下所示:
<%@ Page src="formulaire.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="formulaire" %>
<HTML>
<HEAD>
<title>Formulaire</title>
<script language="javascript">
function effacer(){
alert("Vous avez cliqué sur le bouton [Effacer]");
}
function raz(liste){
liste.selectedIndex=-1
}
</script>
</HEAD>
<body>
<p>
Gestion d'un formulaire
</p>
<hr>
<form name="formulaire" method="post" action="main.aspx?action=validation">
<table border="1">
<tr>
<td>
Etes-vous marié(e)</td>
<td>
<p align="center">
<INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui
<INPUT type="radio" value="non" name="rdMarie" <%=rdnonchecked%>>Non
</p>
</td>
</tr>
<TR>
<TD>Cases à cocher
</TD>
<TD>
<P align="center">
<INPUT type="checkbox" value="un" name="C1" <%=c1checked%>>1
<INPUT type="checkbox" value="deux" name="C2" <%=c2checked%>>2
<INPUT type="checkbox" value="trois" name="C3" <%=c3checked%>>3
</P>
</TD>
</TR>
<TR>
<TD>Champ de saisie</TD>
<TD>
<P align="center">
<INPUT type="text" maxLength="30" value="<%=txtSaisie%>" name="txtSaisie">
</P>
</TD>
</TR>
<tr>
<td>
Mot de passe</td>
<td>
<p align="center">
<input type="password" maxlength="12" size="12" value="<%=txtMdp%>" name="txtMdp">
</p>
</td>
</tr>
<tr>
<td>
Boîte de saisie</td>
<td>
<textarea name="areaSaisie"><%=areaSaisie%></textarea>
</td>
</tr>
<tr>
<td>
ComboBox</td>
<td>
<select name="cmbValeurs">
<%=cmbValeursOptions%>
</select>
</td>
</tr>
<tr>
<td>
Liste à choix simple</td>
<td>
<select size="3" name="lstSimple">
<%=lstSimpleOptions%>
</select>
<INPUT type="button" value="Raz" name="btnRazSimple" onclick="raz(lstSimple)">
</td>
</tr>
<tr>
<td>
Liste à choix multiple</td>
<td>
<select multiple size="3" name="lstMultiple">
<%=lstMultipleOptions%>
</select>
<INPUT type="button" value="Raz" name="btnRazMultiple" onclick="raz(lstMultiple)">
</td>
</tr>
<tr>
<td>
Bouton simple</td>
<td>
<p align="center">
<input onclick="effacer()" type="button" value="Effacer" name="btnEffacer">
</p>
</td>
</tr>
<tr>
<td>
Bouton submit</td>
<td>
<p align="center">
<input type="submit" value="Envoyer" name="btnEnvoyer">
</p>
</td>
</tr>
<tr>
<td>
Bouton reset</td>
<td>
<p align="center">
<input type="reset" value="Rétablir" name="btnRetablir" runat="server">
</p>
</td>
</tr>
</table>
<input type="hidden" name="secret" value="uneValeur">
</form>
</body>
</HTML>
让我们来分析一下代码 HTML 中出现的动态变量:
变量 | 作用 |
如果单选按钮 [oui] 需要被选中,则该变量取值为 "checked";否则取值为 "" | |
单选按钮 [non] 同上 | |
复选框 [C1] 同上 | |
复选框同上 [C2] | |
复选框[C3]亦同 | |
要放入字段中的文本 [txtSaisie] | |
要放入字段 [txtMdp] 中的文本 | |
要放入字段 [areaSaisie] 的文本 | |
下拉列表选项中的文本 HTML [cmbValeurs] | |
下拉列表选项中的文本 HTML [lstSimple] | |
下拉列表选项中的文本 HTML [lstMultiple] | |
这些变量的值由页面控制器 [formulaire.aspx.vb] 计算得出:
Imports Microsoft.VisualBasic
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' 表单的常量字段
Private libellésCmbValeurs() As String = {"choix1", "choix2", "choix3"}
Private valeursCmbValeurs() As String = {"1", "2", "3"}
Private libellésLstSimple() As String = {"liste1", "liste2", "liste3"}
Private valeursLstSimple() As String = {"1", "2", "3"}
Private libellésLstMultiple() As String = {"multiple1", "multiple2", "multiple3", "multiple4", "multiple5"}
Private valeursLstMultiple() As String = {"1", "2", "3", "4", "5"}
' 表单的动态字段
Protected rdouichecked As String
Protected rdnonchecked As String
Protected c1checked As String
Protected c2checked As String
Protected c3checked As String
Protected txtSaisie As String
Protected txtMdp As String
Protected areaSaisie As String
Protected cmbValeursOptions As String
Protected lstSimpleOptions As String
Protected lstMultipleOptions As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' 从会话中获取上一次请求
Dim form As NameValueCollection
If Not Context.Items("formulaire") Is Nothing Then
form = Context.Items("formulaire")
Else
form = New NameValueCollection
End If
' 准备要显示的页面
' 单选按钮
rdouichecked = ""
rdnonchecked = "checked"
If isEqual(form("rdMarie"), "oui") Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' 复选框
c1checked = ""
If isEqual(form("C1"), "un") Then c1checked = "checked"
c2checked = ""
If isEqual(form("C2"), "deux") Then c2checked = "checked"
c3checked = ""
If isEqual(form("C3"), "trois") Then c3checked = "checked"
' 输入字段
txtSaisie = ""
If Not form("txtSaisie") Is Nothing Then txtSaisie = form("txtSaisie").ToString
txtMdp = ""
If Not form("txtMdp") Is Nothing Then txtMdp = form("txtMdp").ToString
areaSaisie = ""
If Not form("areaSaisie") Is Nothing Then areaSaisie = form("areaSaisie").ToString
' 列表
Dim sélections() As String = {}
If Not form("cmbValeurs") Is Nothing Then sélections = form.GetValues("cmbValeurs")
cmbValeursOptions = getOptions(valeursCmbValeurs, libellésCmbValeurs, sélections)
sélections = New String() {}
If Not form("lstSimple") Is Nothing Then sélections = form.GetValues("lstSimple")
lstSimpleOptions = getOptions(valeursLstSimple, libellésLstSimple, sélections)
sélections = New String() {}
If Not form("lstMultiple") Is Nothing Then sélections = form.GetValues("lstMultiple")
lstMultipleOptions = getOptions(valeursLstMultiple, libellésLstMultiple, sélections)
End Sub
Private Function getOptions(ByRef valeurs() As String, ByRef libelles() As String, ByRef sélections() As String) As String
' 生成 <select> 标签选项的代码 HTML
' 值:标签选项的值数组
' 标签:标签选项的标签数组
' 选项:可选选项
Dim iValeur As Integer
Dim iSelection As Integer
Dim selected As String
Dim toString As String = ""
' 遍历选项值列表
For iValeur = 0 To valeurs.Length - 1
' 检查当前值是否应被选中
selected = "" : iSelection = 0
Do While iSelection < sélections.Length And selected = ""
If valeurs(iValeur) = sélections(iSelection) Then selected = "selected"
iSelection += 1
Loop
' 插入该选项的代码 HTML
toString += "<option " + selected + " value='" + valeurs(iValeur) + "'> " _
+ libelles(iValeur) + "</option>" + ControlChars.CrLf
Next
' 返回结果
Return toString
End Function
Private Function isEqual(Byval champ As Object, ByVal valeur As String) As Boolean
' 若字段等于该值,则返回真
If champ Is Nothing OrElse champ.ToString <> valeur Then
Return false
Else
Return true
End If
end function
End Class
当页面 [formulaire.aspx] 开始执行时,它将在其上下文中查找一个名为“form”的键,该键关联着它需要显示的字段值集合。
' 从会话中检索上一次查询
Dim form As NameValueCollection
If Not Context.Items("formulaire") Is Nothing Then
form = Context.Items("formulaire")
Else
form = New NameValueCollection
End If
人们可能会问,为什么要验证 [Context.Items("formulaire")] 是否存在。 事实上,控制器在任何情况下都会为该对象赋值。然而,这并不能阻止客户端绕过控制器直接请求页面 [formulaire.aspx]。如果发生这种情况,上述代码将处理一个空值集合,但不会导致“崩溃”。
该代码会遍历收到的值集合,以计算与其关联的页面 HTML 中的所有动态变量。尽管代码较长,但并不特别复杂,为避免冗长,我们留给读者自行研读。 不过,我们将重点探讨如何根据三个 [select] 类型的列表生成 HTML 代码。该代码由以下函数生成:
Private Function getOptions(ByRef valeurs() As String, ByRef libelles() As String, ByRef sélections() As String) As String
' 返回 <select> 标签选项中的代码 HTML
' 值:标签选项值的数组
' 标签:标签选项的标签数组
' 选项:可选选项
Dim iValeur As Integer
Dim iSelection As Integer
Dim selected As String
Dim toString As String = ""
' 遍历选项值列表
For iValeur = 0 To valeurs.Length - 1
' 检查当前值是否应被选中
selected = "" : iSelection = 0
Do While iSelection < sélections.Length And selected = ""
If valeurs(iValeur) = sélections(iSelection) Then selected = "selected"
iSelection += 1
Loop
' 插入该选项的代码 HTML
toString += "<option " + selected + " value='" + valeurs(iValeur) + "'> " _
+ libelles(iValeur) + "</option>" + ControlChars.CrLf
Next
' 返回结果
Return toString
End Function
需要提醒的是,<select> 标签的选项对应以下代码 HTML:
因此,对于每个选项,需要生成三项信息:
- [value] 属性中的选项值
- 位于 <option> 和 </option> 标签之间的选项文本
- 若该选项需在列表中被选中,则生成关键字 [selected]
为了为每个选项生成这三项信息,函数 [getOptions] 接收三个值:
- [valeurs] 中的选项值数组
- [libelles] 中的选项文本数组
- [sélections] 中的待选值数组
5.5.4. 验证页面
验证页面保持不变:
[validation.aspx]
<%@ Page src="validation.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="validation" %>
<HTML>
<HEAD>
<title>validation</title>
</HEAD>
<body>
<P>Valeurs saisies</P>
<HR width="100%" SIZE="1">
<TABLE id="Table1" cellSpacing="1" cellPadding="1" width="300" border="1">
<TR>
<TD width="84">rdMarie</TD>
<TD><% =rdMarie%></TD>
</TR>
<TR>
<TD width="84">C1</TD>
<TD><%=C1%></TD>
</TR>
<TR>
<TD width="84">C2</TD>
<TD><%=C2%></TD>
</TR>
<TR>
<TD width="84">C3</TD>
<TD><%=C3%></TD>
</TR>
<TR>
<TD width="84">txtSaisie</TD>
<TD><%=txtSaisie%></TD>
</TR>
<TR>
<TD width="84">txtMdp</TD>
<TD><%=txtMdp%></TD>
</TR>
<TR>
<TD width="84">areaSaisie</TD>
<TD><%=areaSaisie%></TD>
</TR>
<TR>
<TD width="84">cmbValeurs</TD>
<TD><%=cmbValeurs%></TD>
</TR>
<TR>
<TD width="84">lstSimple</TD>
<TD><%=lstSimple%></TD>
</TR>
<TR>
<TD width="84">lstMultiple</TD>
<TD><%=lstMultiple%></TD>
</TR>
<TR>
<TD width="84">secret</TD>
<TD><%=secret%></TD>
</TR>
</TABLE>
<P>
<a href="main.aspx?action=formulaire">Retour au formulaire</a>
</P>
</body>
</HTML>
[validation.aspx.vb]
Imports Microsoft.VisualBasic
Imports System.Text.RegularExpressions
Public Class validation
Inherits System.Web.UI.Page
Protected rdMarie As String
Protected C1 As String
Protected C2 As String
Protected C3 As String
Protected txtSaisie As String
Protected txtMdp As String
Protected areaSaisie As String
Protected cmbValeurs As String
Protected lstSimple As String
Protected lstMultiple As String
Protected secret As String
Protected delimiteur As New Regex("\r\n")
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'获取提交的参数
rdMarie = getValue("rdMarie")
C1 = getValue("C1")
C2 = getValue("C2")
C3 = getValue("C3")
txtSaisie = getValue("txtSaisie")
txtMdp = getValue("txtMdp")
areaSaisie = String.Join(",", delimiteur.Split(getValue("areaSaisie")))
cmbValeurs = getValue("cmbValeurs")
lstSimple = getValue("lstSimple")
lstMultiple = getValue("lstMultiple")
secret = getValue("secret")
' 将其保存到会话中
'Session("表单") = Request.Form
End Sub
Private Function getValue(ByVal champ As String) As String
' 获取POST请求中字段[champ]的值
' 是什么?
If Request.Form(champ) Is Nothing Then Return ""
' 获取字段的值
Dim valeurs() As String = Request.Form.GetValues(champ)
Dim valeur As String = ""
Dim i As Integer
For i = 0 To valeurs.Length - 1
valeur += "[" + valeurs(i) + "]"
Next
Return valeur
End Function
End Class
5.5.5. 测试
将文件 [main.aspx, main.aspx.vb,formulaire.aspx,formulaire.aspx.vb, validation.aspx, validation.aspx.vb] 放置在 <application-path> 目录下,并使用参数 (<application-path>,/mvcform3) 启动 Casini。随后访问网址 [http://localhost/mvcform3/main.aspx]。我们得到预初始化的表单:

我们按以下方式填写表单:

我们点击上方的 [Envoyer] 按钮。服务器返回以下响应:

我们使用上面的链接 [Retour au formulaire] 返回表单。我们得到了以下新的回复:

我们确实看到了与提交时完全一致的表单。
5.5.6. 结论
前面的示例向我们展示了,在客户端与服务器之间的请求-响应循环中,可以保持页面的状态。然而,这项工作并不简单。我们将在后续章节中看到,通过 ASP.NET,可以让服务器自行恢复页面的状态。



