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

启动 [WebMatrix] 并选择 [文件/新建文件] 选项:

我们选择创建一个 HTML 页面。上述操作将生成文件 [d:\data\devel\aspnet\chap4\example1\example1.htm]。[Web Matrix] 提供了两种编辑此文件的方式:[设计] 模式和 [HTML] 模式:

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

要从该面板插入元素,只需双击该元素,然后将其放置在 [设计] 窗口中。[HTML] 模式允许您使用文本编辑器构建 HTML 文档。这需要掌握 HTML 语法。在 [HTML] 选项卡中,已生成一个文档骨架:

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

- 从组件面板中添加 [水平线] 组件:

- 添加 [表格] 组件:

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

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

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

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

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

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

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

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

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

- 我们放置链接前面的文本:

- 我们将文本 [here] 转换为指向 URL [http://istia.univ-angers.fr] 的链接。为此,我们先选中该文本,然后选择 [HTML/插入超链接] 选项:

- 结果如下:

- 已经快完成了。让我们在 [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> 部分替换为以下内容:
此外,我们希望文本 [HTML - 1] 显示为更大字号。<Hi>text</Hi> 标签允许您设置文本大小,其中 "i" 的取值范围为 1 到 6,从最大到最小。这里我们将使用 H2。该标签
变为:
[设计]窗口已反映我们的更改:

剩下的就是使用 [View/Start] 选项或 [F5] 进行测试。[WebMatrix] 会要求提供一些信息以启动 [Cassini] Web 服务器:

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

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

我们已经介绍了使用 [WebMatrix] 创建 HTML 页面的基础知识。建议读者尝试创建其他 HTML 页面,并每次检查生成的 HTML 代码。随着练习的深入,大家将能够不使用 [设计] 模式来创建页面。一个 HTML 文档通常具有以下结构:
整个文档被包含在 <html>...</html> 标签之间。它由两部分组成:
<head>...</head>:这是文档中不可显示的部分。它向负责显示文档的浏览器提供信息。该部分通常包含 <title>...</title> 标签,用于设置浏览器标题栏中显示的文本。 该部分还可能包含其他标签,包括定义文档关键词的标签,这些关键词随后会被搜索引擎使用。该部分还可能包含脚本,通常采用 JavaScript 或 VBScript 编写,这些脚本将由浏览器执行。
<body attributes>...</body>:这是浏览器将显示的部分。该部分包含的 HTML 标签向浏览器说明了文档的“预期”视觉布局。每个浏览器对这些标签的解释方式各不相同。因此,两个浏览器可能以不同的方式显示同一个网页文档。这通常是网页设计师面临的挑战之一。
前一个文档的 HTML 代码如下:
<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 | HTML 标签及示例 |
<title>HTML1</title> 当文档显示时,HTML1将出现在浏览器的标题栏中 | |
<horizontal>:显示一条水平线 | |
<table attributes>....</table> : 用于定义表格 <tr attributes>... : 用于定义一行 <td 属性>... : 定义一个单元格 示例: <table border="1">...:border 属性定义表格边框的粗细 <td align="center" width="200">单元格(1,2) : 定义一个内容为单元格(1,2)的单元格。该内容将水平居中(align="center")。该单元格的宽度为 200 像素(width="200") | |
<img src="univ01.gif" />:定义了一张图片,其源文件位于Web服务器上的univ01.gif(src="univ01.gif")。该链接位于可通过URL http://localhost/exemple1.htm 访问的Web文档中。因此,浏览器将请求URL http://localhost/univ01.gif 以获取此处引用的图片。 | |
<a href="http://istia.univ-angers.fr">here: 使文本“here”作为指向 URL http://istia.univ-angers.fr 的链接。 |
在这个简单的示例中,我们可以看到,为了构建整个文档,浏览器必须向服务器发出两次请求:
http://localhost/exemple1.htm 以获取文档的 HTML 源代码
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="form" method="post"> | |
<input type="text" maxlength="30" value="a few words" name="txtInput" /> | |
<input type="password" maxlength="12" size="12" value="aPassword" name="txtMdp" /> | |
<textarea name="inputArea">第1行 行2</textarea> | |
<input type="radio" value="yes" name="rdMarie" />是 <input type="radio" checked value="no" name="rdMarie" />否 | |
<input type="checkbox" value="one" name="C1" />1 <input type="checkbox" checked value="two" name="C2" />2 <input type="checkbox" value="three" 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>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="hidden" name="secret" value="aValue" /> | |
<input type="submit" value="提交" name="submitButton" /> | |
<input type="reset" value="重置" name="btnReset" runat="server" /> | |
<input onclick="clear()" type="button" value="清除" name="btnClear" /> |
让我们回顾一下这些不同的控件。
5.2.2.1. 该
<form name="form" method="post"> |
<form name="..." method="..." action="...">...</form> | |
name="formexample":表单名称 method="..." : 浏览器用于将表单中收集的值发送至 Web 服务器的方法 action="...":表单中收集的值将被发送到的 URL。 Web表单由<form>...</form>标签包围。表单可以指定名称(name="xx")。这适用于表单内的所有控件。如果Web文档中包含需要引用表单元素的脚本,该名称将非常有用。 表单的目的是收集用户通过键盘或鼠标输入的信息,并将其发送至某个 Web 服务器 URL。具体是哪个 URL?即 action="URL" 属性中指定的那个。如果缺少该属性,信息将发送至包含该表单的文档所在的 URL。Web 客户端如何将信息(表单中的数据)发送至 Web 服务器? 我们已对此进行了详细说明。它可以使用两种不同的方法,即 POST 和 GET。<form> 标签的 method="method" 属性(其中 method 设置为 GET 或 POST)会告知浏览器,应使用哪种方法将表单中收集的信息发送至 action="URL" 属性指定的 URL。当未指定 method 属性时,默认使用 GET 方法。 |
5.2.2.2. 输入字段

<input type="text" maxlength="30" value="some words" name="txtInput" /> <input type="password" maxlength="12" size="12" value="aPassword" name="txtMdp" /> |
<input type="..." name="..." size=".." value=".."> input 标签适用于各种控件。正是 type 属性将这些不同的控件区分开来。 | |
type="text":指定这是一个文本输入框 type="password":输入字段中的字符将被星号(*)替换。这是它与普通输入字段的唯一区别。此类控件适用于输入密码。 size="12":字段中可见的字符数——不会阻止输入更多字符 maxlength="30":将最大字符数设置为 30——由浏览器负责执行此属性 name="txtInput":控件的名称 value="some words":将在输入框中显示的文本。 |
5.2.2.3. 多行输入框

<textarea name="inputArea">line1 第2行</textarea> |
<textarea ...>文本</textarea> 显示一个包含初始文本的多行输入框 | |
rows="2": 行数 cols="'20":列数 name="areaSaisie":控件名称 |
5.2.2.4. 单选按钮
![]()
<input type="radio" value="yes" name="rdMarie" />是 <input type="radio" checked value="no" name="rdMarie" />否 |
<input type="radio" attribute2="value2" ..../>text 显示一个旁边带有文本的单选按钮。 | |
name="rdMarie":控件的名称。名称相同的单选按钮构成一组互斥按钮:其中只能选中一个。 value="value":分配给单选按钮的值。请勿将此值与单选按钮旁显示的文本混淆。该文本仅用于显示目的。 checked:如果存在此关键字,则单选按钮被选中;否则,则未被选中。 |
5.2.2.5. 复选框
<input type="checkbox" value="one" name="C1" />1 <input type="checkbox" checked value="two" name="C2" />2 <input type="checkbox" value="three" name="C3" />3 |
![]()
<input type="checkbox" attribute2="value2" ..../>text 显示一个复选框,旁边带有文本。 | |
name="C1":控件的名称。复选框的名称可以相同,也可以不同。名称相同的复选框构成一组关联的复选框。 value="value":分配给复选框的值。请勿将此值与复选框旁显示的文本混淆。该文本仅用于显示目的。 checked:如果存在此关键字,则复选框被选中;否则,则未被选中。 |
5.2.2.6. 下拉列表(组合框)
<select name="cmbValues"> <option value="1">选项1</option> <option value="2" selected>选项2</option> <option value="3">选项 3</option> </select> |
![]()
<select size=".." name=".."> <option [selected="selected"] [value="value"]>文本</option> ... </select> 在列表中显示位于 <option>...</option> 标签之间的文本 | |
name="cmbValues":控件的名称。 size="1":可见列表项的数量。size="1" 使列表等同于一个下拉列表框。如果缺少 [size] 属性,此为默认值。 selected="selected":如果列表项具有此属性,该项在列表中将显示为已选中。在上例中,列表项“choice2”在组合框首次显示时即作为选中项出现。 value="value":设置当该选项被选中时发送给服务器的值。如果缺少此属性,则发送与该选项关联的文本。 |
5.2.2.7. 单选列表
<select size="3" name="lstSimple"> <option value="1" selected>list1</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="value"]>...</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="value"]>...</option> ... </select> 在列表中显示 <option>...</option> 标签之间的文本 | |
multiple:允许在列表中选择多个项目。在上例中,项目 list1 和 list3 均被选中。 |
5.2.2.9. 按钮
<input onclick="clear()" type="button" value="清除" name="btnClear" /> |
![]()
<input type="button" value="..." name="..." onclick="clear()" ....> | |
type="button":定义一个按钮控件。还有另外两种类型的按钮:submit 和 reset。 value="清除":按钮上显示的文本 onclick="function()":允许您定义一个函数,当用户点击按钮时执行该函数。该函数是显示的网页文档中定义的脚本的一部分。上述语法是 JavaScript 语法。如果脚本使用 VBScript 编写,则应写为 onclick="function",不带圆括号。 如果需要向函数传递参数,语法保持不变:onclick="function(val1, val2,...)" 在本示例中,点击“清除”按钮将调用以下 JavaScript 函数: clear 函数会显示一条消息: ![]() |
5.2.2.10. 提交按钮
<input type="submit" value="提交" name="btnSubmit" /> |
![]()
<input type="submit" value="发送" name="btnSend"> | |
type="submit":将该按钮定义为向 Web 服务器发送表单数据的按钮。当用户点击此按钮时,浏览器将使用该标签的 method 属性定义的方法,将表单数据发送至 <form> 标签的 action 属性中定义的 URL。 value="Submit":按钮上显示的文本 |
5.2.2.11. 重置按钮
<input type="reset" value="重置" name="btnReset" runat="server" /> |
![]()
<input type="reset" value="重置" name="btnReset"> | |
type="reset":将该按钮定义为表单重置按钮。当用户点击此按钮时,浏览器将把表单恢复到接收时的状态。 value="重置":按钮上显示的文本 |
5.2.2.12. 隐藏字段
<input type="hidden" name="secret" value="aValue" /> |
<input type="hidden" name="..." value="..."> | |
type="hidden":指定这是一个隐藏字段。隐藏字段是表单的一部分,但不会显示给用户。不过,如果用户要求浏览器显示源代码,他们会看到 <input type="hidden" value="..."> 标签的存在,从而看到隐藏字段的值。 value="aValue":隐藏字段的值。 隐藏字段的用途是什么?它允许 Web 服务器在客户端的多次请求中保留信息。以一个 Web 购物应用程序为例。客户端在目录的第一页购买了初始商品 art1,数量为 q1,然后转到目录中的新页面。为了记住客户端购买了 q1 件 art1 商品,服务器可以在新页面的 Web 表单中将这两条信息放入一个隐藏字段中。 在此新页面上,客户端购买了 q2 件商品 art2。当第二个表单的数据提交至服务器时,服务器不仅会收到 (q2,art2) 信息,还会收到 (q1,art1) 信息——后者作为表单中的隐藏字段,用户无法对其进行修改。 随后,Web 服务器将把信息 (q1,art1) 和 (q2,art2) 放入一个新的隐藏字段中,并发送一个新的目录页面。以此类推。 |
5.3. 通过浏览器将表单值发送至 Web 服务器
从上一章我们已经知道,客户端是如何将信息传输给服务器的。它通常通过以下方式实现:
- HTTP GET 请求:url?param1=va1¶m2=val2&....
- HTTP POST请求,后跟包含参数字符串的文档 param1=va1¶m2=val2&....
浏览器将根据 [form] 标签的 [method] 属性设置为 GET 还是 POST,来选用这两种方法之一。这就是我们接下来要演示的内容。我们之前查看的页面是一个静态页面。为了访问请求此文档的浏览器发送的 HTTP 头,我们将把它转换为一个面向 .NET Web 服务器(IIS 或 Cassini)的动态页面。 将之前的静态代码放入名为 [form_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>
上面的呈现页面与 [form_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
' saves the current query in request.txt of the page folder
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
End Class
每次调用文档 [form_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 服务器。我们请求 URL 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 由用户提供时,情况总是如此。我们按以下方式填写表单:

我们使用上方的 [提交] 按钮。其 HTML 代码如下:
当点击 [提交] 按钮时,浏览器会将表单参数(即 <form> 标签)发送至 <form action="URL"> 标签中 [action] 属性指定的 URL(如果该属性存在)。如果该属性不存在,表单参数将发送至提供该表单的 URL。本例即属于这种情况。 因此,[提交]按钮应触发浏览器向URL [http://localhost/form2/formulaire_get.aspx] 发送包含表单参数的请求。由于页面 [formulaire_get.aspx] 会存储接收到的请求,我们应该能够看到客户端是如何发送这些参数的。让我们试一试。我们点击[提交]按钮。我们从浏览器收到以下响应:

这是初始页面,但您可以看到浏览器 [地址] 栏中的 URL 已发生变化,变为:
http://localhost/form2/formulaire_get.aspx?rdMarie=oui&C1=un&C2=deux&txtSaisie=programmation+asp.net&txtMdp=unMotDePasse&areaSaisie=les+bases+de+la%0D%0Aprogrammation+web%0D%0A&cmbValeurs=3&lstSimple=1&lstMultiple=2&lstMultiple=4&btnEnvoyer=Envoyer&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,其中参数是 Web 表单控件的名称,值则是与之关联的数值。下表为三列表格:
- 第 1 列:显示示例中 HTML 控件的定义
- 第 2 列:展示该控件在浏览器中的显示效果
- 第 3 列:展示浏览器针对第 1 列中的控件,以示例中 GET 请求的形式发送给服务器的值
HTML控件 | 预验证显示 | 返回值 |
<input type="radio" value="yes" name="rdMarie" />是 <input type="radio" checked value="no" name="rdMarie" />否 | - 按钮 value 属性的值 。 | |
<input type="checkbox" value="one" name="C1" />1 <input type="checkbox" checked value="two" name="C2" />2 <input type="checkbox" value="three" name="C3" />3 | C1=one C2=two - 复选框 value 属性的值 用户选中的 | |
<input type="text" maxlength="30" value="some words" name="txtInput" /> | txtSaisie=编程+asp.net - 用户在 中输入的文本。空格已被 为 + 号 | |
<input type="password" maxlength="12" size="12" value="aPassword" name="txtMdp" /> | txtMdp="thisIsSecret" - 用户在 。实际输入的文本是 "thisIsSecret"。最后一个字符 丢失,因为 maxlength="12" 限制了 限制为12个字符。 | |
<textarea name="areaSaisie"> line1 第2行</textarea> | ![]() | areaSaisie=the+basics+of+ the%0D%0A Web+编程 - 用户在 . %OD%OA 是 。空格已被 替换为 + 号 |
<select name="cmbValeurs"> <option value="1">选项1 </option> <option value="2" selected>选项2 </option> <option value="3">选项3 </option> </select> | cmbValues=3 - 用户从 单选列表中 | |
<select size="3" name="lstSimple"> <option value="1" selected>list1</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">multiple4 </option> <option value="5">multiple5 </option> </select> | ![]() | lstMultiple=2 lstMultiple=4 - 用户在 多选列表中 |
<input type="submit" value="提交" name="btnSubmit" /> | btnEnvoyer=提交 - 用于提交表单数据的按钮的 name 和 value 属性 用于将表单数据 至服务器 | |
<input type="hidden" name="secret" value="aValue" /> | secret=aValue - 隐藏字段的 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 服务器已将参数传递给了文档 [http://localhost/form2/formulaire_get.aspx] 中的 URL,即我们最初创建的文档。我们尚未编写任何代码来检索和处理客户端发送给我们的参数。因此,一切都发生得仿佛客户端的请求仅仅是:
这就是为什么,当我们点击[提交]按钮时,收到的页面与最初不带参数请求 URL [http://localhost/form2/formulaire_get.aspx] 时获得的页面完全相同。
5.3.2. POST 方法
现在,HTML文档已配置为让浏览器使用POST方法将表单值发送至Web服务器。为此,我们将[form_get.aspx]文件复制为[form_post.aspx],并仅修改[form_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">
无需修改控制器 [form_get.aspx.vb],因此我们保持原样。我们通过 URL [http://localhost/form2/formulaire_post.aspx] 请求新文档,像使用 GET 方法时那样填写表单,并使用 [Submit] 按钮将参数提交给服务器。我们从服务器收到以下响应页面:

因此,我们得到的结果与 GET 方法相同,即初始页面。请注意一个区别:在浏览器的 [地址] 栏中,传输的参数不会显示。现在,让我们看看客户端发送并保存在文件 [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请求中的编码完全相同。 | |
“提交”的字符数,即 Web 服务器在接收 HTTP 头部后,为检索客户端发送的文档必须读取的字符数。此处的文档即表单值列表。 | |
指定客户端在HTTP头部之后将发送的文档类型。类型 [application/x-www-form-urlencoded] 表示这是一个包含表单值的文档。 |
向 Web 服务器传输数据有两种方法:GET 和 POST。哪种方法更好?我们已经看到,如果浏览器使用 GET 方法发送表单值,浏览器会在地址栏中显示请求的 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 应用程序:
- 第一个视图是前面的表单。我们将它命名为 [form_view]。
- 第二个视图是一个信息页面,列出了在第一个页面中输入的值。该页面提供了一个链接,允许用户返回表单。我们将此视图命名为 [view_confirmation]。
[form_view] 被发送给用户,用户填写并提交表单。提交前的界面可能如下所示:

用户点击 [提交] 按钮提交其输入内容。随后,他们将收到以下 [验证视图]:

5.4.2. 应用程序控制器
我们在上一章中看到,可以通过 [global.asax] 文件为 MVC 应用程序分配控制器角色,所有客户端请求都会经过该文件。我们已经展示了一个以此方式构建的 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)
' retrieve the action to be performed
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' execute the action
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:当用户点击[提交]按钮时,其输入的值将通过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>
点击 [清除] 按钮将取消选中其关联列表中的所有项目。 以 [lstMultiple] 列表为例。点击对应的 [清除] 按钮将触发 JavaScript 函数 [raz(lstMultiple)] 的执行。请注意,浏览器显示的 HTML 文档中的 JavaScript 代码是由浏览器本身执行的,而非由服务器执行。JavaScript 是一种功能非常全面的语言,允许您在无需服务器干预的情况下为页面添加动态功能。[raz] 函数如下:
该函数接受一个参数,该参数是一个代表 HTML 列表的 JavaScript 对象。该对象具有属性和方法。其中一个属性是 [selectedIndex],其值是 HTML 列表中第一个被选中选项的编号。如果没有被选中的选项,该属性值为 -1。反之,为该属性设置一个值会选中 HTML 列表中的新项目,将其设置为 -1 则表示未选中任何项目。这就是此处所做的操作。
最后,请注意,呈现代码 [form.aspx] 并未配有控制器 [form.aspx.vb]。实际上,该 HTML 文档中不包含任何由服务器生成的动态内容,因此无需控制器。
5.4.4. 处理验证操作
当控制器处理“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
'retrieve posted parameters
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
' retrieves the value of field [field] from the posted request
' anything?
If Request.Form(champ) Is Nothing Then Return ""
' retrieve the value(s) of the
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=line1\r\nline2\r\n... 的形式,其中 \r 是 ASCII 码 13(回车),\n 是 ASCII 码 10(换行)。 因此,函数 getValue(areaSaisie) 返回的字符串为 "line1\r\nline2\r\n..."。该字符串通过 [Regex.Split] 方法按行拆分,从而得到字符串数组 {line1,line2,...}。 该数组通过 [String.Join] 方法转换为字符串 "line1,line2,..."。最终显示的 [areaSaisie] 字段值即为此字符串。此处的目的是演示如何从 HTML [TextArea] 字段中提取单行内容。
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]脚本会先运行,并最终显示[form.aspx]页面。这就是为什么[default.aspx]文件可以是空的。
假设经过验证的表单如下所示:

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

首先,请注意响应 URL:[http://localhost/mvcform1/?action=validation]。这是 [formulaire.aspx] 表单中 <form> 标签的 [action] 属性的 URL:
<form name="formulaire" method="post" action="?action=validation">
让我们逐一检查不同表单字段的获取值:
字段 | 值 | HTML | comments |
rdMarie | 是 | <input type="radio" value="是" name="rdMarie">是 <input type="radio" checked value="no" name="rdMarie">否 | 获得的值是复选框的 [value] 属性 |
C1 | a | <input type="checkbox" value="one" name="C1">1 | 相同 |
C2 | 二 | <input type="checkbox" 已选中 value="two" name="C2">2 | 相同 |
C3 | <input type="checkbox" value="three" name="C3">3 | 用户未选中此按钮。因此,浏览器未发送其值。在代码中,条件 [Request.Form("C3") is Nothing] 因此为真。 | |
txtInput | 编程 ASP.NET | <input type="text" ... name="txtSaisie"> | 返回的值是验证时输入字段中的文本 |
txtMdp | 密码 | <input type="password" ...name="txtMdp"> | 相同 |
输入字段 | 基础 Web 编程 | <textarea name="areaSaisie"> ...</textarea> | 相同 |
cmbValues | 3 | <select name="cmbValues"> ... <option value="3">选项3</option> </select> | 获取的值是所选选项的 [value] 属性 |
lstSimple | 3 | <select size="3" name="lstSimple"> .... <option value="3">list3</option> ... </select> | 相同 |
lstMultiple | 2 4 | <select multiple size="3" name="lstMultiple"> ... <option value="2">multiple2 </option> ... <option value="4">multiple4 </option> ... </select> | 获取的值是所选选项的 [value] 属性的值 |
secret | aValue | <input type="hidden" name="secret" value="aValue"> | 获取的值即为隐藏字段的 [value] 属性 |
现在我们来看看列表。假设在验证时,表单的状态如下:

- 下拉框中选中了 [choice1] 选项
- 其他两个列表中未选中任何选项。我们使用 [Clear] 按钮实现了这一点。
以下是验证后返回的页面:

当列表中未选中任何值时,浏览器不会为此发送参数。因此,在 [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] 属性值为一个包含单个参数 [action=form] 的 URL,这使服务器能够知道必须以表单提交时的原始状态显示该表单。这与显示预定义表单的 [init] 操作不同。 包含 [?action=form] 参数的 URL 实际上并不存在。因此,它将与显示提交页面的 URL 相同。正如我们在前一个示例中所见,这是应用程序文件夹的 URL。因此,请求将通过控制器处理。控制器现在必须处理 [form] 操作。对 [global.asax.vb] 中的代码进行如下修改:
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
' retrieve the action to be performed
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' execute the action
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
如果操作是“form”,我们只需将请求转发到 [form.aspx] 页面。我们知道该页面显示的是一个预定义的表单,其中不包含可变部分。因此,该页面无法显示表单在提交时所包含的值。我们在此试图强调这一点。 如果所有应用程序文件都已放置在 <application-path> 中,我们使用参数 (<application-path>,/mvcform2) 启动 Cassini,然后请求 URL [http://localhost/mvcform2]。我们将看到以下页面(部分视图):

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

我们将获得以下确认页面:

我们点击上图页面中存在的(但未显示的)[返回表单]链接。服务器返回以下响应:

我们观察到:
- 请求的 URL 确实如预期包含 action=form 参数
- 显示的表单已丢失已输入的值。
我们知道为何无法检索已输入的值。如果查看 [form.aspx] 文档的代码,会发现所有内容都是静态的,因此每次都会强制显示相同的页面。显然,我们需要让这段代码变得动态。它需要显示用户已验证的值。但我们应该将它们存储在哪里呢?
让我们回顾一下无状态 HTTP 协议的铁律:
- 请求 URL [http://localhost/mvcform2] —— 收到响应。在请求开始时,客户端与服务器之间建立了一条 TCP-IP 连接,并在响应结束时关闭。
- 用户输入并提交数据。请求 URL [http://localhost/mvcform2/?action=validation] —— 收到响应。双方之间建立并关闭了一个新的 TCP/IP 连接。
- 用户点击 [返回表单] 链接。请求 URL [http://localhost/mvcform2/?action=formulaire] —— 收到响应。双方之间建立并关闭了一个新的 TCP/IP 连接。
这些请求-响应循环彼此独立,因为每个循环都使用新的 TCP-IP 连接。当客户端-服务器应用程序使用单个 TCP-IP 连接进行一系列交互时,服务器可以通过连接来识别客户端。因此,服务器可以存储与特定连接相关联的信息,从而跟踪交互过程。当交互通过不同的连接进行时,服务器无法通过连接来识别客户端。此时需要另一种方法。 我们在上一章中介绍了一种此类方法:会话机制。它允许请求-响应循环将信息存储在 [Session] 对象中,后续所有循环均可访问该对象。
既然我们现在拥有了一个支持会话的 MVC 应用程序,就不能再像上一章那样使用 [global.asax] 文件作为控制器了。应用程序控制器的角色必须由专门为此目的设计的特定页面来承担。在此,该页面即为 [main.aspx]。我们将按以下步骤进行:
- 当用户提交数据(action=validation)时,[main.aspx.vb] 控制器会在显示验证页面之前,将数据存储到当前会话中。
- 当需要显示包含已输入值的表单(action=form)时,控制器 [main.aspx.vb] 会在显示表单前从会话中检索这些值并将其放入上下文中。
5.5.2. 新的应用程序控制器
应用程序控制器由两个文件组成 [main.aspx, main.aspx.vb]:
[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
' retrieve the action to be performed
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' execute the action
Select Case action
Case "init"
' the pre-filled form is displayed
Context.Items("formulaire") = initForm()
Server.Transfer("formulaire.aspx", True)
Case "validation"
' the confirmation page is displayed after saving the values posted in the session
Session.Item("formulaire") = Request.Form
Server.Transfer("validation.aspx", True)
Case "formulaire"
' displays the form with values taken from the session
Context.Items("formulaire") = Session.Item("formulaire")
Server.Transfer("formulaire.aspx", True)
Case Else
' the pre-filled form is displayed
Context.Items("formulaire") = initForm()
Server.Transfer("formulaire.aspx", True)
End Select
End Sub
Private Function initForm() As NameValueCollection
' initialize the form
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] 中存在的表单值将存储在与“form”键关联的会话中。然后,执行将转移到 [validation.aspx] 页面,该页面将显示这些值。
- 对于其他操作,执行在所有情况下都会转移到 [formulaire.aspx] 页面。该页面期望在其上下文中有一个“formulaire”键,该键将与类型为 [Request.Form](即类型为 [NameValueCollection])的对象相关联。该对象必须包含表单字段的值集合。
- 如果操作是 [init] 或未识别的操作,则由 [initForm] 函数任意构建该值集合。
- 如果操作是 [form],则该集合即为 [validation] 操作放置在会话中的 [Request.Form] 集合
5.5.3. 新表单
由于应用程序不再在表单中显示相同的内容,因此必须动态生成表单。新的 [form.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代码中出现的动态变量:
变量 | 作用 |
如果应选中 [yes] 单选按钮,则该变量的值为 "checked";否则值为 "" | |
[no] 单选按钮的情况亦同 | |
[C1] 复选框的情况亦同 | |
[C2] 复选框也是如此 | |
[C3] 复选框也是如此 | |
要放入 [txtInput] 字段中的文本 | |
要放入 [txtPassword] 字段的文本 | |
要放入 [areaInput] 字段的文本 | |
用于 [cmbValeurs] 下拉列表中选项的 HTML 文本 | |
[lstSimple] 下拉列表中选项的 HTML 文本 | |
[lstMultiple] 下拉列表中选项的 HTML 文本 | |
这些变量的值由页面控制器 [formulaire.aspx.vb] 计算得出:
Imports Microsoft.VisualBasic
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' constant form fields
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"}
' dynamic form fields
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
' retrieve the previous request from the session
Dim form As NameValueCollection
If Not Context.Items("formulaire") Is Nothing Then
form = Context.Items("formulaire")
Else
form = New NameValueCollection
End If
' prepare the page to be displayed
' radio buttons
rdouichecked = ""
rdnonchecked = "checked"
If isEqual(form("rdMarie"), "oui") Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' checkboxes
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"
' input fields
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
' lists
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
' renders HTML code for <select> tag options
' values: table of tag option values
' labels: table of tag option labels
' selections: options to select
Dim iValeur As Integer
Dim iSelection As Integer
Dim selected As String
Dim toString As String = ""
' browse the list of option values
For iValeur = 0 To valeurs.Length - 1
' check whether the current value should be selected
selected = "" : iSelection = 0
Do While iSelection < sélections.Length And selected = ""
If valeurs(iValeur) = sélections(iSelection) Then selected = "selected"
iSelection += 1
Loop
' we integrate the HTML code from the
toString += "<option " + selected + " value='" + valeurs(iValeur) + "'> " _
+ libelles(iValeur) + "</option>" + ControlChars.CrLf
Next
' we return the result
Return toString
End Function
Private Function isEqual(Byval champ As Object, ByVal valeur As String) As Boolean
' returns true if field is equal to value
If champ Is Nothing OrElse champ.ToString <> valeur Then
Return false
Else
Return true
End If
end function
End Class
当 [form.aspx] 页面开始运行时,它将在其上下文中找到一个与需要显示的字段值集合相关的“form”键。
' retrieve the previous request from the session
Dim form As NameValueCollection
If Not Context.Items("formulaire") Is Nothing Then
form = Context.Items("formulaire")
Else
form = New NameValueCollection
End If
有人可能会疑惑,为何要检查 [Context.Items("form")] 是否存在。实际上,控制器在所有情况下都会为该对象赋值。然而,客户端完全可以绕过控制器直接请求 [form.aspx] 页面。如果发生这种情况,上述代码虽然会处理一个空值集合,但并不会导致“崩溃”。
该代码会遍历收到的值集合,以计算关联 HTML 页面中的所有动态变量。尽管代码较长,但并不特别复杂,为避免冗长,我们将留给读者自行探索。不过,我们将重点关注如何生成三个 [select] 下拉列表的 HTML 代码。该代码由以下函数生成:
Private Function getOptions(ByRef valeurs() As String, ByRef libelles() As String, ByRef sélections() As String) As String
' renders HTML code for <select> tag options
' values: table of tag option values
' labels: table of tag option labels
' selections: options to select
Dim iValeur As Integer
Dim iSelection As Integer
Dim selected As String
Dim toString As String = ""
' browse the list of option values
For iValeur = 0 To valeurs.Length - 1
' check whether the current value should be selected
selected = "" : iSelection = 0
Do While iSelection < sélections.Length And selected = ""
If valeurs(iValeur) = sélections(iSelection) Then selected = "selected"
iSelection += 1
Loop
' we integrate the HTML code of the
toString += "<option " + selected + " value='" + valeurs(iValeur) + "'> " _
+ libelles(iValeur) + "</option>" + ControlChars.CrLf
Next
' we return the result
Return toString
End Function
请注意,<select> 标签的选项对应以下 HTML 代码:
因此,对于每个选项,需要生成三项信息:
- [value] 属性中的选项值
- 位于 <option> 和 </option> 标签之间的选项文本
- 如果该选项应在列表中被选中,则为 [selected] 关键字
为了为每个选项生成这三项信息,[getOptions] 函数接收三个值:
- [values] 中的选项值数组
- [labels] 中的选项文本数组
- [selections] 中待选值的数组
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
'retrieve posted parameters
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")
' save them in the session
'Session("formulaire") = Request.Form
End Sub
Private Function getValue(ByVal champ As String) As String
' retrieves the value of field [field] from the posted request
' anything?
If Request.Form(champ) Is Nothing Then Return ""
' retrieve the value(s) of the
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, form.aspx, form.aspx.vb, validation.aspx, validation.aspx.vb] 放置在 <application-path> 目录下,并使用参数 (<application-path>,/mvcform3) 启动 Casini。随后我们请求 URL [http://localhost/mvcform3/main.aspx]。我们得到预初始化的表单:

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

点击上方的 [提交] 按钮。我们收到来自服务器的以下响应:

我们使用上方的 [返回表单] 链接返回表单。收到如下新响应:

我们看到表单与提交时完全一致。
5.5.6. 结论
前面的示例向我们展示了,在客户端与服务器之间的请求-响应循环中,完全有可能保持页面的状态。不过,这并非易事。我们将在后面的章节中看到,借助 ASP.NET,可以让服务器自动恢复页面的状态。



