7. 服务器组件 ASP - 1
7.1. Introduction
本章将介绍 ASP.NET 中推荐用于构建用户界面的技术。我们知道,Web 服务器处理 .aspx 页面时包含两个截然不同的阶段:
- 首先是页面控制器的执行。该控制器由位于 .aspx 页面本身(WebMatrix 方案)或单独文件(Visual Studio.NET 方案)中的代码构成。
- 随后,.aspx 页面的呈现代码被执行,并转换为 HTML 代码发送给客户端。

ASP.NET 提供了三种标签库用于编写页面的呈现代码:
- 经典的 HTML 标签。这是我们迄今为止所使用的。
- 服务器端 HTML 标签
- WebForms 标签
无论使用哪种标签库,页面控制器的作用都不会改变。它必须计算呈现代码中出现的动态参数的值。迄今为止,这些动态参数都很简单:它们是 [String] 类型的对象。因此,如果在呈现代码中有一个 <%=nom%> 标签:
- 页面控制器会声明一个类型为 [String] 的变量 [nom] 并计算其值
- 当页面控制器完成工作且渲染代码被执行以生成响应 HTML 时,标签 <%=nom%> 将被控制器代码计算出的值所替换
众所周知,控制器与呈现层的分离是任意的,且可以在同一页面中混合使用控制器代码和呈现代码。我们已解释过为何不建议采用这种方法,并将继续遵循控制器与呈现层的分离原则。
标签 [HTML serveur] 和 [WebForms] 允许在呈现代码中引入比简单对象 [String] 更复杂的对象。这有时确实很有用。 以包含列表的表单为例。该列表应通过类似以下格式的 HTML 代码呈现给客户:
<select name="uneListe" size="3">
<option value="opt1">option1</option>
<option value="opt2">option2</option>
<option value="opt3" selected>option3</option>
</select>
列表内容和待选选项均为动态元素,因此必须由页面控制器生成。我们曾遇到过此问题,并通过在呈现代码中添加
该标签将被变量 [uneListeHTML] 的值 [String] 替换。 该值由控制器计算得出,应为列表中 HTML 的代码,即 c.a.d。“<select name=..>...</select>”。 这并不特别难实现,而且看起来是一个优雅的解决方案,避免了将生成代码直接放入页面的呈现部分。否则,那里将出现一个需要插入测试条件的循环,从而严重“污染”该部分。然而,这种方法有一个缺点。页面控制与呈现的分离也用于划分两个职责领域:
- 负责页面控制器的开发者 .NET
- 负责页面展示部分的图形设计师
在此,我们可以看到列表的代码生成(HTML)已被转移至控制器中。 界面设计师可能需要修改该代码 HTML 来调整列表的“视觉”效果。这将迫使他介入 [contrôleur] 部分,从而超出其职责范围,并可能因此无意中在代码中引入错误。
服务器标记库解决了这一问题。它们提供了一个代表列表 HTML 的对象。因此,库 [WebForms] 提供了以下标记:
该标签表示一个 [ListBox] 类型的对象,可由页面控制器进行操作。该对象具有若干属性,用于表示 HTML 列表中的不同选项,并指定所选选项。 因此,页面控制器将为这些属性赋予相应的值。当执行呈现部分时,标签
将被代码 HTML 替换,该代码代表对象 [uneListe]、c.a.d。 代码“<select ..>...</select>”。目前,这与前一种方法没有根本区别,只是采用了一种面向对象的编码方式,这很有意思。让我们回到我们的图形设计师,他需要修改列表的“外观”。 服务器标签具有样式属性(BackColor、Bordercolor、BorderWidth 等),可用于设定对应对象 HTML 的视觉外观。因此我们可以编写如下代码:
<asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>
其优势在于,图形设计师无需离开呈现代码即可进行这些修改。这相较于前一种方法无疑是一大优势。因此,服务器标记库为构建网页的呈现部分(即前几章中所称的用户界面)提供了便利。本章旨在介绍这些库。 我们将看到,这些库有时会提供诸如日历或与数据源关联的表格等复杂对象。它们具有可扩展性,c.a.d,用户可以创建自己的标签库。例如,用户可以创建一个用于在页面中生成横幅的标签。所有使用该标签的页面都将显示相同的横幅。
针对某个标签生成的内容会根据客户端浏览器的类型进行适配。 当客户端向Web服务器发送请求时,会在其HTTP请求头中包含一个[User-Agent: xx]头部,其中[xx]用于标识客户端。以下是一个示例:
凭借这些信息,Web 服务器可以了解客户端的能力,特别是它能够处理的 HTML 代码类型。事实上,随着时间的推移,HTML 语言已经出现了多个版本。 新版浏览器能够支持该语言的最新版本,而旧版浏览器则无法做到这一点。 根据客户端发送的 HTTP [User-Agent:] 标头,服务器将向其发送一个它能够理解的 HTML 版本。 这是一个既有趣又实用的思路,因为这样开发者就无需担心应用程序的客户端浏览器类型了。
最后,像Visual Studio.NET、WebMatrix等高级版本的IDE,支持以“Windows风格”设计Web界面。虽然这些工具并非必不可少,但能为开发人员提供决定性的帮助。 开发者可通过在界面中放置图形组件来绘制Web界面。他可以直接访问界面中每个组件的属性,并根据需要进行设置。 这些属性将被转换为界面呈现代码(HTML)中组件的<asp:>标签属性。这对开发人员的好处在于,他们无需记住每个标签属性的列表及其语法。 当开发者尚未完全掌握 ASP.NET 提供的服务器标记库时,这一优势尤为显著。一旦掌握了该语法,部分开发者可能会更倾向于直接在页面呈现代码中编写标记,而跳过图形设计阶段。 此时,IDE便不再必要,仅需一个简单的文本编辑器即可。根据工作方式的不同,重点将放在组件上(使用IDE)或标签上(使用文本编辑器)。 这两个术语是等效的。组件是页面控制代码将要操作的对象。IDE使我们能够在设计阶段访问其属性。 为其设定的值会立即转换为呈现代码中组件标签的属性。在运行阶段,页面控制代码将操作该组件,并为其某些属性赋值。 呈现代码将利用两部分信息生成该组件的 HTML 代码:一方面是设计阶段为对应服务器标签设定的属性,另一方面是控制代码计算出的组件属性值。
7.2. 示例的运行环境
我们将通过以下主要运行环境的程序,来演示基于服务器组件的 Web 界面设计:
- Web 应用程序由单个页面 P 组成,其中包含表单 F,
- 客户端将直接向该页面 P 发出首次请求。这将通过浏览器请求页面 P 的 URL 来实现。因此,将针对该 URL P 发出请求 GET。服务器将返回页面 P 及其所包含的表单 F,
- 用户填写表单并提交,即 c.a.d。用户执行的操作将触发浏览器将表单 F 提交至服务器。 浏览器的 POST 操作仍将指向页面 P。服务器将再次返回包含表单 F 的页面 P,此时表单 F 的内容可能已被用户操作修改。
- 随后将重新执行步骤2和步骤3。
这是一个非常特殊的执行流程,若脱离此流程,下文所述的某些概念将无法正常运作。我们不再处于MVC架构的语境中——在该架构中,多页面应用程序由我们称为“应用程序控制器”的特定页面进行控制。 在此类架构中,表单的 POST 请求目标是控制器,而非表单本身。然而我们将看到,使用服务器端组件构建表单意味着该表单会被提交至自身。
7.3. Label组件
7.3.1. 用法
<asp:label> 标签允许在页面的呈现代码中插入动态文本。因此,它所做的无非是迄今为止使用的 <%=variable%> 标签所能做到的。 通过研究这个首个标签,我们将了解服务器端标签的工作机制。我们创建一个包含控件部分 [form1.aspx.vb] 和呈现部分 [form1.aspx] 的页面。该页面用于显示时间:

该问题已在第2章中讨论过,若读者想了解具体处理方式,请参阅该章节。呈现代码[form1.aspx]如下:
<%@ page src="form1.aspx.vb" inherits="form1" AutoEventWireup="false" %>
<HTML>
<HEAD>
<title>Webforms</title>
</HEAD>
<body>
<asp:Label Runat="server" ID="lblHeure" />
</body>
</HTML>
我们引入了 <asp:label> 标签。在标签库中,[runat="server"] 属性是必填的。ID 属性用于标识该组件。 控制器必须使用该标识符进行引用。控制器 [form1.aspx.vb] 的代码如下:
Imports System.Web.UI.WebControls
Public Class form1
Inherits System.Web.UI.Page
Protected lblHeure As Label
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
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' 将时间写入 lblHeure
lblHeure.Text = "Il est " + Date.Now.ToString("T")
End Sub
End Class
控制器必须为类型为 [System.Web.UI.WebControls.Label] 的对象 [lblHeure] 赋值。所有由 <asp:> 标签显示的对象都属于命名空间 [System.Web.UI.WebControls]。因此,我们可以系统地导入该命名空间:
Imports System.Web.UI.WebControls
对象 [Label] 具有多个属性,其中 [Text] 属性代表将由对应的 <asp:label> 标签显示的文本。在此,我们将当前时间赋值给该属性。 该操作在控制器中的 [Form_Load] 过程内完成,该过程会始终被执行。 在 [Form_Init] 过程(该过程同样始终执行,但位于 [Form_Load] 过程之前)中,我们将客户端的请求存储在应用程序文件夹中的 [request.txt] 文件中。 我们将有机会分析该文件,以理解使用服务器标记的页面运作机制的某些方面。
对象 [Label] 拥有众多属性、方法和事件。建议读者查阅 [Label] 类的文档以了解详情。后续内容也将遵循这一做法。对于每个标签,我们仅介绍其中我们需要的少数几个属性。
7.3.2. 测试
我们将文件(form1.aspx、form1.aspx.vb)放置在<application-path>文件夹中,并使用参数(<application-path>,/form1)启动 Cassini。随后访问 URL [http://localhost/form1/form1.aspx]。结果如下:

浏览器接收到的 HTML 代码如下:
<HTML>
<HEAD>
<title>Webforms</title>
</HEAD>
<body>
<span id="lblHeure">Il est 19:39:37</span>
</body>
</HTML>
可以看到服务器标签
<asp:Label Runat="server" ID="lblHeure" />
已被转换为以下代码 HTML:
这是对象 [lblHeure] 的属性 [Text],已被放置在 <span> 和 </span> 标签之间。客户端发出的请求已存储在 [request.txt] 中,内容如下:
GET /form1/form1.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
一切都很正常。
7.3.3. 使用 WebMatrix 构建应用程序
我们手动编写了页面 [form1.aspx] 的呈现代码。如果熟悉标签语法,这种方法是可行的。此时只需一个简单的文本编辑器即可构建用户界面。对于初学者而言,通常需要结合图形设计工具和自动代码生成功能,因为我们并不了解所需标签的语法。 现在,我们将使用工具 WebMatrix 构建相同的应用程序。启动 WebMatrix 后,我们选择选项 [File/New File]:

我们创建一个名为 [form2.aspx] 的页面。确认前一个向导后,将显示页面 [form2.aspx] 的设计窗口:


需要提醒的是,WebMatrix 将页面的控制代码和呈现代码合并到同一个文件中,即此处的 [form2.aspx]。[All] 选项卡显示了该文本文件的内容。现在就可以看到,该文件并非空文件:

此类工具的缺点在于,它们往往会生成无用的代码。本例中便是如此,WebMatrix 生成了一个 <form> 标签,而我们并不打算构建表单…… 此外,还可以发现该文档缺少 <title> 标签。我们立即解决了这两个问题,得到了以下新版本:

我们所说的控制器代码将插入在 <script> 和 </script> 标签之间,这确保了控制代码与呈现代码之间至少在视觉上的分离。我们回到 [Design] 选项卡来设计界面。设计窗口左侧的工具窗口中提供了一份组件列表:

该工具窗口提供两种类型的组件:
- [WebControls] 组件,其对应生成 <asp:> 标签
- [HTML Elements] 组件,其对应的是经典的 HTML 标签。不过,可以在 HTML 标签的属性中添加 [runat="server"] 属性。 在这种情况下,控制器可以通过一个对象访问 HTML 标签及其属性,该对象的属性与其所代表的 HTML 标签的属性相同。此前,我们称这些标签为服务器 HTML 标签。
双击控件列表 [WebControls] 中的组件 [Label]。在 [Design] 选项卡中,将得到以下结果:

在 [All] 选项卡中,代码变为如下所示:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="Label1" runat="server">Label</asp:Label>
</body>
</html>
首先可以注意到,我们丢失了 <script> 标签。生成了一个 <asp:label> 标签。它的名称为 [Label1],值为 [Label]。 返回 [Design] 选项卡以修改这两个值。在此处单击 [Label] 组件一次,以在右下角显示该组件的属性窗口:

请读者查看 [Label] 对象的属性。其中有两项是我们关注的重点:
- Text:这是标签应显示的文本——我们将字符串设为空(c.a.d. nothing)
- ID:这是其标识符——我们设置为 lblHeure
[Design] 选项卡内容变为:

而 [All] 的代码变为:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
页面的布局部分已经完成。接下来我们需要编写控制代码,将时间填入 [Text] 的属性中(该属性属于 [lblHeure])。我们在 [All] 选项卡中添加以下代码:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' 将时间写入 lblHeure
lblHeure.Text = "Il est " + Date.Now.ToString("T")
End Sub
</script>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
需要注意的是,在控制器代码中,并未像之前那样声明对象 [lblHeure]:
Protected lblHeure As New System.Web.UI.WebControls.Label
实际上,呈现层中的所有 <asp:> 服务器控件在控件代码部分都会被隐式声明。因此,如果再次声明它们,会引发编译错误,错误信息会指出该对象已被声明。 现在可以执行了。我们选择选项 [View/Start] 或快捷方式 [F5]。Cassini 将自动启动并使用以下参数:

我们接受这些值。系统默认浏览器将自动打开并请求网址 [http://localhost/form2.aspx]。我们得到以下结果:

此后,我们将主要使用工具 WebMatrix 来简化我们编写的小型程序的构建和测试工作。
7.4. Literal组件
7.4.1. 用法
<asp:literal> 标签与 <asp:label> 标签类似,可用于在页面的呈现代码中插入动态文本。其主要属性为 [Text],该属性表示将原样插入到页面 HTML 流中的文本。 如果无意对要插入到 HTML 流中的文本进行格式化,仅使用此标签即可。 事实上,虽然类 [Label] 允许通过诸如 [BorderColor, BorderWidth, Font, ...] 之类的属性进行格式设置,但类 [Literal] 却不具备任何此类属性。 读者可完全沿用前面的示例,只需将组件 [Label] 替换为组件 [Literal]。
7.5. Button组件
7.5.1. 用法
<asp:Button> 标签允许在表单中插入一个 [Submit] 类型的按钮,该按钮带来的事件处理机制与 Windows 应用程序中的类似。本文将重点探讨这一点。我们创建以下 [form3.aspx] 页面:

该页面由 WebMatrix 构建,包含三个组件:
编号 | 名称 | 类型 | 属性 | 作用 |
1 | Button | text=按钮1 | 提交按钮 | |
2 | Button | text=按钮2 | 提交按钮 | |
3 | 标签 | text= | 提示信息 |
WebMatrix为本部分生成的代码如下:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="Button1" runat="server" Text="Bouton1"></asp:Button>
<asp:Button id="Button2" runat="server" Text="Bouton2"></asp:Button>
</p>
<p>
<asp:Label id="lblInfo" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
我们在<asp:>标签中看到了页面图形设计时使用的组件[Button]和[Label]。请注意自动生成的<form runat="server">标签。 这是一个服务器端标签 HTML,c.a.d。一个经典的 HTML 标签,但由控制器可操作的对象表示。 <form> 标签的代码将根据控制器赋予该对象的值进行生成。
在代码的控制器部分,添加处理页面事件 [Init] 的过程 [Page_Init]。我们将保存客户端请求到文件 [request.txt] 的代码放置在此处。 我们需要这些代码来理解按钮的工作机制。
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' 将当前请求保存到页面文件夹中的 request.txt
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
</script>
需要注意的是,我们并未在过程声明 [Page_Init] 之后添加子句 [Handles MyBase.Init]。 实际上,对象 [Page] 的事件 [Init] 有一个名为 [Page_Init] 的默认处理程序。如果使用该处理程序名称,则 [Handles Page.Init] 子句便不再必要。 不过,即使保留该子句也不会引发错误。
7.5.2. 测试
我们通过 [F5] 在 WebMatrix 下启动应用程序。我们得到以下页面:

浏览器接收到的 HTML 代码如下:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo"></span>
</p>
</form>
</body>
</html>
请注意以下几点:
- <form runat="server"> 标签已转换为 HTML 标签
已设置两个属性:[method="post"] 和 [action="form3.aspx"]。能否为这些属性赋予不同的值?我们稍后将对此进行说明。这里需要记住的是,表单将提交至 URL [form3.aspx]。 另外还设置了两个属性 [name, id]。大多数情况下,这些属性会被忽略。但是,如果页面包含在浏览器端执行的 JavaScript 代码,则 <form> 标签的 [name] 属性就很有用。
- <asp:button> 标签已变为 HTML 按钮标签。 因此,点击其中任何一个按钮都会触发表单 [_ctl10] 向 URL [form3.aspx] 提交数据。
- <asp:label> 标签已变为 HTML <span> 标签
- 生成了一个隐藏字段 [__VIEWSTATE],其值非常奇怪:
该字段以编码形式表示发送给客户端的表单状态。该状态代表表单中所有组件的值。 由于 [__VIEWSTATE] 是表单的一部分,其值将与其他内容一同提交至服务器。这将使服务器能够识别表单中哪个组件的值发生了变化,并据此做出相应决策。这些决策将以“TextBox 某项的值已更改”这类事件的形式呈现。
7.5.3. 客户端的请求
在浏览器中获取 [form3.aspx] 页面后,我们重新请求该页面,并查看浏览器为获取该页面而发送的请求。请注意,我们的应用程序会将其存储在应用程序文件夹中的 [request.txt] 文件中:
GET /form3.aspx 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
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
这里有一个典型的 GET。现在,让我们在浏览器中点击页面上的 [Bouton1] 按钮。表面上看似乎什么也没发生。 然而我们知道表单已被提交。页面上的代码HTML证实了这一点。[request.txt]的新内容也证实了这一点:
POST /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 80
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/form3.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button1=Bouton1
第一个头部 HTTP 确实表明客户端已向 URL [/form3.aspx] 发送了 POST。最后一行显示了提交的值:
- 隐藏字段 __VIEWSTATE 的值
- 被点击的按钮的值
如果我们点击 [Bouton2],浏览器提交的值如下:
隐藏字段的值始终不变,但提交的是 [Button2] 的值。因此,服务器可以知道使用了哪个按钮。它将利用此信息触发一个事件,该事件可在页面加载完成后由页面进行处理。
7.5.4. 处理 Button 对象的 Click 事件
回顾一下 .aspx 页面的工作原理。该页面是一个从类 [Page] 派生的对象。我们将这个派生类命名为 [unePage]。 当服务器收到针对此类页面的请求时,会通过 new unePage(...) 操作实例化一个 [unePage] 类型的对象。 随后,服务器按此顺序生成两个名为 [Init] 和 [Load] 的事件。 [unePage] 对象可以通过提供 [Page_Init] 和 [Page_Load] 事件处理程序来管理这些事件。随后将生成其他事件。 我们稍后将再次讨论这一点。如果客户端的请求是 POST,服务器将生成由触发该 POST 的按钮所引发的 [Click] 事件。 如果类 [unePage] 为该事件定义了处理程序,则该处理程序将被调用。让我们通过 WebMatrix 来了解这一机制。 在 [form3.aspx] 的 [Design] 选项卡中,双击按钮 [Bouton1]。 此时系统会自动跳转至 [Code] 选项卡,进入名为 [Button1_Click] 的过程主体。为了更好地理解,我们转到 [All] 选项卡,查看完整的代码。已进行以下修改:
<%@ Page Language="VB" %>
<script runat="server">
...
Sub Button1_Click(sender As Object, e As EventArgs)
End Sub
</script>
<html>
...
<body>
<form runat="server">
...
<asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
</form>
</body>
</html>
在 [Button1] 的 <asp:Button> 标签中添加了一个新属性 [onclick="Button1_Click"]。 该属性指定了负责处理 [Click] 事件的存储过程,该事件发生在 [Button1] 对象上,此处为存储过程 [Button1_Click]。接下来我们只需编写该存储过程:
Sub Button1_Click(sender As Object, e As EventArgs)
' 点击按钮 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
该过程在标签 [lblInfo] 中放置了一条信息提示。我们对按钮 [Bouton2] 采用相同的方法,从而得到以下新的页面 [form3.aspx]:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' 将当前请求保存到页面文件夹中的 request.txt
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Sub Button1_Click(sender As Object, e As EventArgs)
' 点击按钮 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
Sub Button2_Click(sender As Object, e As EventArgs)
' 点击按钮 2
lblInfo.Text="Vous avez cliqué sur [Bouton2]"
End Sub
</script>
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
<asp:Button id="Button2" onclick="Button2_Click" runat="server" Text="Bouton2" BorderStyle="None"></asp:Button>
</p>
<p>
<asp:Label id="lblInfo" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
我们通过 [F5] 启动执行,以获取以下页面:

如果查看收到的 HTML 代码,我们会发现它与该页面上一版本的代码相比没有变化:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo"></span>
</p>
</form>
</body>
</html>
如果我们点击 [Bouton1],将得到以下响应:

针对此响应收到的代码 HTML 如下:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwO3Q8O2w8aTwxPjs+O2w8dDw7bDxpPDU+Oz47bDx0PHA8cDxsPFRleHQ7PjtsPFZvdXMgYXZleiBjbGlxdcOpIHN1ciBbQm91dG9uMV07Pj47Pjs7Pjs+Pjs+Pjs+4oO98Vd244kj0lPMXReWOwJ1WW0=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo">Vous avez cliqué sur [Bouton1]</span>
</p>
</form>
</body>
</html>
我们可以看到,隐藏字段 [__VIEWSTATE] 的值发生了变化。这反映了组件 [lblInfo] 的值发生了变化。
7.5.5. ASP.NET 应用程序生命周期事件
文档 ASP.NET 列出了服务器在应用程序生命周期中生成的事件:
- 当应用程序接收到第一个请求时,将生成应用程序对象 [HttpApplication] 上的事件 [Start]。 该事件可通过应用程序文件 [global.asax] 中的过程 [Application_Start] 进行处理。
随后,对于收到的每个请求,将有一系列事件循环触发:
- 如果请求未发送会话令牌,则会启动一个新会话,并在与该请求关联的 [Session] 对象上生成 [Start] 事件。 该事件可通过应用程序中 [global.asax] 文件中的 [Session_Start] 过程进行处理。
- 服务器在对象 [HttpApplication] 上生成事件 [BeginRequest]。该事件可通过应用程序中文件 [global.asax] 中的过程 [Application_BeginRequest] 进行处理。
- 服务器加载请求所要求的页面。它将实例化一个 [Page] 对象,然后在此对象上生成两个事件:[Init] 以及 [Load]。 这两个事件可通过页面中的 [Page_Init] 和 [Page_Load] 过程进行处理。
- 根据收到的提交值,服务器将生成其他事件: 当组件 [TextBox] 的值发生变化时触发 [TextChanged];当单选按钮的值发生变化时触发 [CheckedChanged];当列表中选中的项目发生变化时触发 [SelectedIndexChanged],等等。 在介绍后续的各个服务器组件时,我们将逐一说明其主要事件。针对名为 O 的对象上的每个事件 E,均可由名称为 O_E 的过程进行处理。
- 上述事件的处理顺序无法保证。因此,处理程序不应对此顺序做任何假设。不过可以确定的是,引发 POST 的按钮事件 [Click] 会被最后处理。
- 页面准备就绪后,服务器将把它发送给客户端。在此之前,它会生成事件 [PreRender],该事件可由页面的 [Page_PreRender] 过程处理。
- 一旦响应 HTML 发送给客户端,页面将从内存中卸载。此时将生成两个事件:[Unload] 和 [Disposed]。页面可以利用这些事件来释放资源。
应用程序还可能在客户端请求之外接收事件:
- 当会话结束时,应用程序中 [Session] 对象会触发 [End] 事件。这可能是页面代码的显式请求所致,也可能是因为会话的预设生命周期已过期。 文件 [global.asax] 中的过程 [Session_End] 负责处理此事件。通常,该过程会释放从 [Session_Start] 中获取的资源。
- 当应用程序结束时,应用程序的 [HttpApplication] 对象上会触发 [End] 事件。这通常发生在停止 Web 服务器时。 文件 [global.asax] 中的过程 [Application_End] 负责处理此事件。该过程通常会释放 [Application_Start] 中获取的资源。
需注意以下几点:
- 先前的事件模型基于传统的 HTTP 客户端-服务器通信。通过查看交换的 HTTP 报头,这一点非常明显。
- 先前事件的处理始终在服务器端进行。点击按钮当然可以由服务器端的 JavaScript 脚本处理。但这并非服务器端事件,而是属于独立于 ASP.NET 的技术范畴。
无论事件是在服务器端(与服务器组件相关的事件)还是通过JavaScript脚本在浏览器端处理,它们是在何时被处理的?
以下拉列表为例。当用户更改其中选中的项时,该事件(选中项的变更)可能被处理,也可能不被处理;若被处理,其处理时机可能各不相同。
- 如果希望立即处理,有两种解决方案:
- 可以通过JavaScript脚本由浏览器进行处理。此时服务器不参与处理。要实现这一点,页面必须能够利用页面中已有的值进行重建。
- 该事件可由服务器处理。为此,只有一种解决方案:表单必须提交至服务器进行处理。因此,我们使用操作 [submit]。 我们将看到,在此情况下,会使用一个名为 [DropDownList] 的服务器组件,并将它的 [AutoPostBack] 属性设置为 [true]。 这意味着,如果下拉列表中选中的元素发生变化,表单必须立即提交至服务器。 此时,服务器会为对象 [DropDownList] 生成一个代码 HTML,该代码关联了一个 JavaScript 函数,该函数负责在发生“选中项更改”事件时立即执行 [submit]。 该 [submit] 将表单提交至服务器,其中会设置隐藏字段以表明 [post] 源自下拉列表中的选择变更。 随后服务器将生成 [SelectedIndexChanged] 事件,页面可对此进行处理。
- 如果希望处理该事件但并非立即处理,则将服务器组件 [DropDownList] 的 [AutoPostBack] 属性设置为 [false]。 在此情况下,服务器将为对象 [DropDownList] 生成经典的 <select> 列表代码 HTML,且不关联任何 JavaScript 函数。因此,当用户更改下拉列表的选择时,不会发生任何变化。 然而,当用户通过例如 [submit] 按钮提交表单时,服务器将能够识别出选项已发生变更。我们确实看到,发送到服务器的表单中包含一个隐藏字段 [__VIEWSTATE],该字段以编码形式表示了所发送表单中所有元素的状态。 当服务器接收到客户端提交的新表单时,它将能够验证下拉列表中选中的项目是否发生了变化。如果发生了变化,它将生成事件 [SelectedIndexChanged],页面随后可以处理该事件。 为了将此机制与前一种机制区分开来,一些作者指出,当“选择变更”事件在浏览器上发生时,该事件已被“缓存”。只有当浏览器向服务器提交表单时(通常是在点击[submit]按钮之后),服务器才会处理该事件。
- 最后,如果不想处理该事件, 只需将服务器组件 [DropDownList] 的属性 [AutoPostBack] 设置为 [false],并省略其事件处理程序 [SelectedIndexChanged] 的编写。
一旦理解了事件处理机制,开发人员就不会再将 Web 应用程序设计成 Windows 应用程序那样。 事实上,虽然Windows应用程序中下拉列表的选项变更可以立即改变其所在表单的外观,但在Web应用程序中,如果处理该事件会触发表单向服务器“提交”(即客户端与服务器之间的网络往返),开发人员通常会更谨慎地决定是否立即处理该事件。 因此,服务器组件的 [AutoPostBack] 属性默认被设置为 [false]。 此外,基于 Web 服务器在发送给客户端的表单中自动生成的 JavaScript 脚本的机制 [AutoPostBack],仅在确信客户端浏览器已允许在其上执行 JavaScript 脚本时才能使用。因此,表单通常按以下方式构建:
- 表单的服务器端组件将其属性设置为 [AutoPostBack] 至 [false]
- 表单包含一个或多个负责执行表单提交操作([POST])的按钮
- 在页面的控制器代码中,仅编写需要处理的事件处理程序,通常是某个按钮上的 [Click] 事件。
7.6. TextBox组件
7.6.1. 用法
<asp:TextBox> 标签可用于在页面的呈现代码中插入一个输入字段。我们创建一个 [form4.aspx] 页面,以获得以下呈现效果:

4321此页面由 WebMatrix 生成,包含以下组件:
编号 | 名称 | 类型 | 属性 | 作用 |
1 | TextBox | AutoPostback=true Text= | 输入框 | |
2 | TextBox | 自动回发=false Text= | 输入框 | |
3 | 标签 | text= | 关于 [TextBox1] 内容的说明信息 | |
3 | 标签 | text= | 关于 [TextBox2] 内容的说明信息 |
WebMatrix为本部分生成的代码如下:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form runat="server">
<p>
Texte 1 :
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True"></asp:TextBox>
</p>
<p>
Texte 2 :
<asp:TextBox id="TextBox2" runat="server"></asp:TextBox>
</p>
<p>
<asp:Label id="lblInfo1" runat="server"></asp:Label>
</p>
<p>
<asp:Label id="lblInfo2" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
在 [Design] 选项卡中,双击 [TextBox1] 组件。 该对象的 [TextChanged] 事件管理器的骨架随即生成([All] 选项卡):
<%@ Page Language="VB" %>
<script runat="server">
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
End Sub
</script>
<html>
...
<body>
...
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
</p>
....
</form>
</body>
</html>
已将属性 [OnTextChanged="TextBox1_TextChanged"] 添加到标签 <asp:TextBox id="TextBox1"> 标签中添加了 [OnTextChanged="TextBox1_TextChanged"] 属性,用于指定 [TextChanged] 事件的处理程序为 [TextBox1]。现在,我们将编写 [TextBox1_Changed] 过程。
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' 修改文本
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
在该流程中,我们在标签 [lblInfo1] 中写入一条消息,用于标记该事件并说明 [TextBox1] 的内容。 对于 [TextBox2] 也采用同样的做法。我们还标注了时间,以便更好地追踪事件的处理过程。[form4.aspx] 的最终代码如下:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' 将当前请求保存到页面文件夹中的 request.txt
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' 修改文本
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
Sub TextBox2_TextChanged(sender As Object, e As EventArgs)
' 文本更改
lblInfo2.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox2]. Texte 2=["+textbox2.Text+"]"
End Sub
</script>
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form runat="server">
<p>
Texte 1 :
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
</p>
<p>
Texte 2 :
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
</p>
<p>
<asp:Label id="lblInfo1" runat="server"></asp:Label>
</p>
<p>
<asp:Label id="lblInfo2" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
我们添加了过程 [Page_Init],用于存储客户请求,如前例所示。
7.6.2. 测试
我们通过 [F5] 启动 WebMatrix 应用程序。我们得到以下页面:

浏览器接收到的 HTML 代码如下:
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
</body>
</html>
该代码中有很多内容是由服务器自动生成的。请注意以下几点:
- 有三个隐藏字段:我们之前已经见过的 [__VIEWSTATE],以及 [__EventTarget] 和 [__EventArgument]。 后两个字段用于处理输入框 [TextBox1] 的浏览器“change”事件
- 服务器标记 <asp:textbox> 生成了与输入字段对应的标记 HTML <input type="text" ...>
- 服务器标记 <asp:textbox id="TextBox1" AutoPostBack="true" ...> 生成了一个带有 [onchange="__doPostBack('TextBox1','')"] 属性的 <input type="text" ...> 标记。 该属性表示,当 [TextBox1] 的内容发生变化时,应执行 JavaScript 函数 [_doPostBack(...)]。该函数如下:
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
上述函数的作用是什么?它为两个隐藏字段 [__EventTarget] 和 [__EventArgument] 分别赋值,然后提交表单。因此,该表单会被发送至服务器。这就是 [AutoPostBack] 的作用。 浏览器“change”事件会触发代码“__doPostBack('TextBox1','')”的执行。 由此可推断,在提交的表单中,隐藏字段 [__EventTarget] 的值为 'TextBox1',而隐藏字段 [__EventArgument] 的值为 ''。 这将使服务器能够识别出引发 POST 的组件。
- 服务器标记 <asp:textbox id="TextBox2"...> 生成了一个常规的 <input type="text" ...> 标记,因为其 [AutoPostBack] 属性未设置为 [true]。
- <form> 标签表示表单将提交至 [form4.aspx]:
让我们进行首次测试。在第一个输入框中输入文本:

然后将光标移至第二个输入框。随即,我们会看到一个新页面:

发生了什么?当光标离开第一个输入框时,浏览器检查了该输入框的内容是否发生了变化。 内容确实发生了变化。因此,浏览器在 HTML 和 [TextBox1] 字段上触发了 [change] 事件。 随后我们看到,一个 JavaScript 函数被执行,并将表单提交至 [form4.aspx]。因此,该页面被服务器重新加载。表单提交的值使服务器进而得知服务器标签 [TextBox1] 的内容已发生变化。 因此,服务器端执行了 [TextBox1_Changed] 过程。该过程在 [lblInfo1] 标签中放置了一条消息。 该过程完成后,[form4.aspx]被发送至浏览器。这就是为什么现在[lblInfo1]中有了文本。话虽如此,输入字段[TextBox1]中出现内容可能会让人感到惊讶。 事实上,没有任何服务器端执行的程序会为该字段赋值。这体现了ASP.NET网页表单的一般机制:服务器会以接收时的状态将表单返回。为此,它会将客户端为其提交的值重新赋给各个组件。 对于某些组件,客户端不会提交任何值。例如,<asp:label> 组件就是如此,它们会被转换为 <span> 标签。需要记住的是,表单有一个隐藏字段,它代表表单在发送给客户端时的状态。 该状态是表单中所有组件状态的总和,包括其中的 <asp:label> 组件(如有)。由于隐藏字段 [__VIEWSTATE] 由客户端浏览器提交,服务器能够还原表单中所有组件的先前状态。 此时服务器只需修改那些值已被 POST 更改的组件即可。
现在让我们在 [request.txt] 中查看浏览器发出的请求:
POST /form4.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 137
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/form4.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=
我们可以清楚地看到 POST 以及已提交的参数。回到浏览器,在第二个输入框中输入文本:

回到第 1 个输入框输入新文本:这次没有任何反应。 为什么?因为输入框 [TextBox2] 没有 [AutoPostBack] 到 [true] 的属性,因此为其生成的 <input type="text"...> 无法处理 [Change] 事件,其代码 HTML 如下所示:
因此,当离开第 2 个输入字段时,不会触发任何事件。现在,让我们在第 1 个字段中输入新文本:

离开第 1 号输入字段。该字段上的事件 [Change] 随即被检测到,表单被提交至服务器,服务器返回以下页面:

发生了什么?首先,浏览器提交了表单。这一事实体现在存储于 [request.txt] 中的客户端请求中:
POST /form4.aspx HTTP/1.1
....
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTt0PDtsPGk8MT47PjtsPHQ8O2w8aTwxPjtpPDU%2BOz47bDx0PHA8cDxsPFRleHQ7PjtsPHByZW1pZXIgdGV4dGU7Pj47Pjs7Pjt0PHA8cDxsPFRleHQ7PjtsPDE4OjQyOjI5OiBldnQgW1RleHRDaGFuZ2VkXSBzdXIgW1RleHRCb3gxXS4gVGV4dGUgMT1bcHJlbWllciB0ZXh0ZV07Pj47Pjs7Pjs%2BPjs%2BPjs%2BxLOermpUUUz5rTAa%2FFsjda6lVmo%3D&TextBox1=troisi%C3%A8me+texte&TextBox2=second+texte
服务器首先通过客户端发送的隐藏字段 [__VIEWSTATE],将组件恢复为之前的值。 借助提交的字段 [TextBox1] 和 [TextBox2],服务器将把提交给它的值分配给组件 [TextBox1] 和 [TextBox2]。 正是通过这一机制,客户端才能还原出表单被提交时的原始状态。随后,服务器仍通过已提交的字段 [__VIEWSTATE]、 [TextBox1] 和 [TextBox2],服务器将发现输入字段 [TextBox1] 和 [TextBox2] 的值已发生变化。 因此,它将为这两个对象生成 [TextChanged] 事件。 将执行 [TextBox1_TextChanged] 和 [TextBox2_TextChanged] 过程,标签 [labelInfo1] 和 [labelInfo2] 将获得新值。 随后,经过修改的页面 [form4.aspx] 将返回给客户端。
现在我们再次修改第 1 个输入字段:

当光标离开字段 1 时,浏览器上会触发事件 [Change]。随后,将执行之前已说明的事件序列(浏览器向服务器发送请求、……、服务器发送响应)。最终得到以下响应:

根据每条消息显示的时间,我们可以看到服务器上仅执行了 [TextBox1_Changed] 过程。[TextBox2_TextChanged] 过程未被执行,正是因为 [TextBox2] 的值没有发生变化。 最后,我们在第2个字段中输入新文本:

然后将光标移至第 1 号字段,再移回第 2 号字段。页面没有变化。为什么?因为我们没有更改第 1 号字段的值,因此当我们离开该字段时,浏览器事件 [Change] 不会触发。 因此,表单并未提交至服务器。所以页面没有任何变化。正是 [lblInfo2] 的内容未发生变化这一事实,表明并不存在 POST。 如果存在该对象,服务器会检测到 [TextBox2] 的内容已发生变化,并应将此变化反映在 [lblInfo2] 中。
从这个例子中可以看出,将 [TextBox] 的 [AutoPostBack] 属性设置为 [true] 毫无意义。这在大多数情况下会导致不必要的客户端-服务器往返。
7.6.3. 字段 __VIEWSTATE 的作用
我们看到,服务器会在生成的表单中系统性地添加一个名为 __VIEWSTATE 的隐藏字段。我们曾提到,该字段代表表单的状态,如果将此隐藏字段返回给服务器,服务器便能还原表单的先前值。表单的状态是其各组件状态的总和。 每个组件都有一个名为 [EnableViewState] 的布尔属性,用于指示该组件的状态是否应被放入隐藏字段 [__VIEWSATE] 中。 默认情况下,该属性的值为 [true],这意味着表单中所有组件的状态都会被放入 [__VIEWSTATE] 中。有时,这种行为并不理想。
让我们进行一些实验,以更好地理解属性 [EnableViewState] 的作用。将此属性设置为 [false],适用于以下两个输入字段:
...
<asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True" EnableViewState="False"></asp:TextBox>
...
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged" EnableViewState="False"></asp:TextBox>
...
现在运行应用程序,在第 1 号字段中输入第一段文本,然后转到第 2 号字段。随后向服务器发送一个 POST 请求,并得到以下响应:

在第2个字段中输入一段文本,修改第1个字段的内容,然后返回第2个字段(按此顺序操作)。向服务器发送了一个新的POST请求,我们得到了以下响应:

目前,一切如常。现在修改第1个字段的内容,然后切换到第2个字段。系统会发出新的POST请求。服务器的响应如下:

这次出现了变化。由于[lblInfo2]的时间被修改,服务器在第2个字段上检测到了[TextChanged]事件。然而实际上并没有发生任何变化。 这可以归因于 [TextBox2] 的属性 [EnableViewState=false]。该属性导致服务器未将 [TextBox2] 的先前状态填入表单的 [__VIEWSTATE] 字段中。 这相当于将其前一状态设为空字符串。 当由于 [TextBox1] 的内容发生变化而产生 POST 时,服务器将 [TextBox2] 的当前值(即 [deux])与其前一个值(空字符串)进行了比较。 服务器据此判定 [TextBox2] 的值已发生变化,并为 [TextBox2] 生成了事件 [TextChanged]。 我们可以通过将空字符串放入 [TextBox2] 来验证这一行为。根据上述说明,服务器不应为 [TextBox2] 生成事件 [TextChanged]。让我们试一试:

结果确实如此。[lblInfo2]的时间和内容表明,[TextBox2_TextChanged]过程并未被执行。基于此,让我们检查表单中四个组件的[EnableViewState]属性:
希望保留该组件的状态,以便服务器知道它是否发生了变化 | |
同上 | |
我们不希望维护该组件的状态。我们希望每次新的 POST 时都重新计算文本。如果未重新计算,则应为空。通过 [EnableViewState=false] 可以很好地实现这一切 | |
同上 |
我们的展示页面变为如下所示:
...
<asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True"></asp:TextBox>
...
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
....
<asp:Label id="lblInfo1" runat="server" enableviewstate="False"></asp:Label>
...
<asp:Label id="lblInfo2" runat="server" enableviewstate="False"></asp:Label>
...
让我们重做之前的那组测试。在之前出现
version 1

,现在显示的是:
version 2

在此步骤中,我们修改了字段1的内容,但未修改字段2的内容,导致服务器端未执行过程 [TextBox2_TextChanged],这意味着字段 [lblInfo2] 未接收新值。因此,它显示的是之前的值。 在版本 1([EnableViewState=true])中,该先前值即为输入的值。而在版本 2([EnableViewState=false])中,该先前值为空字符串。
有时无需保留组件的先前状态。与其为每个组件设置 [EnableViewState=false],不如声明页面无需保持其状态。这可在呈现代码的 [Page] 指令中实现:
在此情况下,无论其 [EnableViewState] 属性取何值,组件的状态都不会被写入隐藏字段 [__VIEWSTATE]。此时,系统会将其前一状态视为空字符串。
现在,让我们使用客户端 [curl] 来阐明其他机制。首先,我们请求 URL [http://localhost/form4.aspx]:
dos>curl --include --url http://localhost/form4.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Sun, 04 Apr 2004 17:51:14 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1077
Connection: Close
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
</body>
</html>
我们从服务器收到了 HTML 的代码,该代码源自 [form4.aspx]。它与浏览器收到的代码并无不同。在此回顾一下,当浏览器提交表单时发出的请求:
POST /form4.aspx HTTP/1.1
Connection: keep-alive
...
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=
让我们执行相同的 POST 操作,但不要发送字段 [__VIEWSTATE]:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=第一段文本 --data TextBox2=
...................
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">19:57:48: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
..............
请注意以下几点:
- 服务器在 [TextBox1] 上检测到了 [TextChanged] 事件,因为它生成了文本 [lblInfo1]。缺少 [__VIEWSTATE] 并未对其造成影响。 在该字段缺失的情况下,它会假设输入字段的上一值是空字符串。
- 它能够将提交给 [TextBox1] 的文本重新放入 [TextBox1] 标签的 [value] 属性中,从而使 [TextBox1] 字段重新显示所输入的值。 为此,他不需要 [__VIEWSTATE],只需 [TextBox1] 的提交值即可
现在,让我们在不做任何更改的情况下重新执行相同的查询。我们将得到以下新的响应:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=第一段文本 --data TextBox2=
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">20:05:47: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
由于缺少 [__VIEWSTATE],服务器无法识别 [TextBox1] 字段的值未发生变化。因此,它将之前的值视为空字符串。 因此,它在此处针对 [TextBox1] 生成了事件 [TextChanged]。让我们再次执行相同的请求,但这次将字段 [TextBox1] 设为空,而 [TextBox2] 不为空:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox2=second+文本 --data TextBox1=
......
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" value="second texte" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2">20:11:54: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
</p>
......
由于缺少 [__VIEWSTATE],因此将 [TextBox1] 的先前值视为空字符串。 由于 [TextBox1] 的已发布值也是空字符串,因此未生成 [TextChanged] 上的 [TextBox1] 事件。 过程 [TextBox1_TextChanged] 未被执行,因此字段 [lblInfo1] 未收到新值。已知在此情况下,组件会保留其旧值。 但此处情况并非如此,[lblInfo1] 已丢失其先前值。这是因为该值是在 [__VIEWSTATE] 中查找的。由于该字段不存在,因此将空字符串赋值给 [lblInfo1]。 对于 [TextBox2],服务器将其提交值 [second texte] 与先前值进行了比较。由于 [__VIEWSTATE] 不存在,该先前值即为空字符串。 由于 [TextBox2] 的发布值不同于空字符串,因此在 [TextBox2] 上生成了事件 [TextChanged]。 已执行过程 [TextBox2_TextChanged],且字段 [lblInfo2] 已获得新值。
人们可能会质疑参数 [__EVENTTARGET] 和 [__EVENTARGUMENT] 是否真的有用。如果不发送这些参数,服务器将无法知道是哪一个事件触发了 [submit]。让我们试一试:
dos>curl --include --url http://localhost/form4.aspx --data TextBox2=第二+文本 --data TextBox1=第一+文本
..............................
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
.....................
可以看到,没有处理任何 [TextChanged] 事件。此外,已提交的字段 [TextBox1] 和 [TextBox2] 无法恢复其提交时的值。 实际上,情况就好像执行了 GET 一样。如果将字段 [__EVENTTARGET] 包含在提交的字段中,即使它没有值,一切也会恢复正常:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET= --data TextBox2=第二个+文本 --data TextBox1=第一个+文本
.......
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" value="second texte" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">20:34:14: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2">20:34:14: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
</p>
........
7.6.4. TextBox 组件的其他属性
服务器组件 [TextBox] 还可生成 HTML <input type="password"..> 和 <textarea>..</textarea> 标签,以及 c.a.d。 这些标签分别对应于受保护的输入字段和多行输入字段。此生成操作由 [TextBox] 组件的 [TextMode] 属性控制。该属性有三个可能的值:
值 | 生成的 HTML 标签 |
<input type="text" ...> | |
<textarea>...</textarea> | |
<input type="password ...> |
我们将通过以下示例 [form5.aspx] 来探讨这些属性的用法:

编号 | 名称 | 类型 | 属性 | 角色 |
1 | 按钮 | 按钮 [submit] - 用于将 [TextBox1] 的内容添加到 [TextBox2] 的内容中 | ||
2 | TextBox | TextMode=Password Text= | 受保护的输入字段 | |
3 | TextBox | TextMode=多行 Text= | 汇总了在 [TextBox1] 中输入的数据 |
页面的 [EnableViewState] 属性被设置为 [false]。在服务器端,我们处理 [btnAjouter] 按钮的点击事件:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' 将 [textBox1] 的内容添加到 [TextBox2] 的内容中
textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub
要理解这段代码,需要回顾表单中 POST 的处理机制。首先执行的是 [Page_Init] 和 [Page_Load] 过程,随后是所有已缓存的事件过程。 最后,处理触发 [POST] 事件的程序被执行,此处即为 [btnAjouter_Click] 程序。当事件处理程序执行时,页面中所有在 POST 中具有值的组件都已采用该值。 其余组件若其 [EnableViewState] 属性值为 [true],则恢复为先前值;若其 [EnableViewState] 属性值为 [false],则恢复为设计值。 在此,字段 [TextBox1] 和 [TextBox2] 的值将作为客户生成的 POST 的一部分。 此外,在上述代码中,[textbox1.text] 的值将取自客户提交的值,[textbox2.text] 亦是如此。 过程 [btnAjouter_Click] 将 [TextBox2] 的提交值与 [TextBox1] 的提交值相加,再与行结束标记 [ControlChars.CrLf],该标记定义在命名空间 [Microsoft.VisualBasic] 中。无需导入该命名空间,Web 服务器会默认导入它。
[form5.aspx]的最终代码如下:
<%@ Page Language="VB" EnableViewState="False" %>
<script runat="server">
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' 将 [textBox1] 的内容添加到 [TextBox2] 的内容中
textbox2.text+=textbox1.text+controlchars.crlf
End Sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter" EnableViewState="False"></asp:Button>
<asp:TextBox id="TextBox1" runat="server" TextMode="Password" Width="353px" EnableViewState="False"></asp:TextBox>
</p>
<p>
<asp:TextBox id="TextBox2" runat="server" TextMode="MultiLine" Width="419px" Height="121px" EnableViewState="False"></asp:TextBox>
</p>
</form>
</body>
</html>
稍早前,我们展示了运行时的屏幕截图。
7.7. 组件 DropDownList
<asp:DropDownList> 标签可用于在页面的呈现代码中插入下拉列表。我们创建一个名为 [form6.aspx] 的页面,以获得如下呈现效果:

编号 | 名称 | 类型 | 属性 | 角色 |
1 | DropDownList | AutoPostback=true EnableViewState=true | 下拉列表 | |
2 | 标签 | EnableViewState=false | 信息提示 |
生成的呈现代码如下:
Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
</p>
<p>
<asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
</p>
</form>
</body>
</html>
目前,下拉列表中尚无任何项目。我们将通过过程 [Page_Load] 来填充它。为此,我们需要了解类 [DropDownList] 的某些属性和方法:
下拉列表项的 [ListItemCollection] 类型集合。该集合的成员类型为 [ListItem]。 | |
集合 [Items] 中的元素数量 | |
列表中的第 i 个元素 - 类型为 [ListItem] | |
向集合 [Items] 中添加新元素 [ListItem] | |
以删除集合 [Items] 中的所有元素 | |
用于从集合 [Items] 中删除第 i 个元素 | |
集合 [Items] 中第 1 个元素 [ListItem],其属性 [Selected] 为真 | |
集合 [Items] 中元素 [SelectedItem] 的编号 |
类 [DropDownList] 下的集合 [Items] 中的元素类型为 [ListItem]。 每个 [ListItem] 元素都会生成一个 HTML 标签 <option>:
我们描述类 [ListItem] 的某些属性和方法:
构造函数 - 创建一个具有属性 [texte] 和 [value] 的 [ListItem] 元素。 一个 ListItem(T,V) 元素将生成 HTML <option value="V">T</option> 标签。 因此,[ListITem]类可用于描述HTML列表中的元素 | |
布尔值。若为真,则列表 HTML 中对应的选项将具有属性 [selected="selected"]。该属性指示浏览器,在列表 HTML 中,对应的元素应显示为被选中状态 | |
选项 HTML 的文本 T <option value="V" [selected="selected"]>T</option> | |
选项 HTML 的属性 [Value] 的值 V <option value="V" [selected="selected"]>T</option> |
我们已掌握足够的信息,可在页面中的 [Page_Load] 过程内,编写用于填充下拉列表 [DropDownList1] 的代码:
Sub Page_Load(sender As Object, e As EventArgs)
' 如果这是第一次被调用,则填充下拉列表
if not IsPostBack then
dim valeurs() as String = {"1","2","3","4"}
dim textes() as String = {"un","deux","trois","quatre"}
dim i as integer
for i=0 to valeurs.length-1
DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
next
end if
end sub
在初始化了[DropDownList1]组件后,其转换结果HTML如下:
<select name="DropDownList1" id="DropDownList1" onchange="__doPostBack('DropDownList1','')" language="javascript">
<option value="1">un</option>
<option value="2">deux</option>
<option value="3">trois</option>
<option value="4">quatre</option>
</select>
我们知道,每次执行页面 [form6.aspx] 时,都会执行过程 [Page_Load]。 该页面首次由 GET 调用,随后在用户每次从下拉列表中选择新项目时,由 POST 调用。 是否需要每次都在 [Page_Load] 中执行该下拉列表的填充代码?答案取决于组件 [DropDownList1] 的 [EnableViewState] 属性。 如果该属性为真,则表明 [DropDownList1] 组件的状态将在后续请求中通过隐藏字段 [__VIEWSTATE] 予以保留。该状态包含两部分:
- 下拉列表中的所有值列表
- 该列表中选中项的值
因此,将组件 [DropDownList1] 的属性 [EnableViewState] 设置为 [true] 似乎是一个诱人的做法,这样就无需重新计算列表中应填入的值。 但问题在于,由于每次调用页面 [form6.aspx] 时都会执行过程 [Page_Load],因此这些值仍会被重新计算。 [Page] 对象([form6.aspx] 是其一个实例)拥有一个布尔值属性 [IsPostBack]。如果该属性为真,则表示该页面是由 POST 调用的。 若为假,则表示该页面是由 GET 调用的。在我们的客户端-服务器往返系统中,客户端总是向服务器请求同一页面 [form6.aspx]。 第一次请求时使用 GET,后续请求则使用 POST。由此可知,属性 [IsPostBack] 可用于检测客户端的首次 GET 调用。 下拉列表的值仅在首次调用时生成。后续请求中,这些值将由 [VIEWSTATE] 机制生成。在其他情况下,列表内容可能因请求而异,因此必须在每次请求时重新计算。 此时,应将该下拉列表的 [EnableViewState] 属性设置为 [false],以避免对列表内容进行不必要的重复计算——除非需要了解列表中先前选中的项目,因为该信息保存在 [VIEWSTATE] 中。
列表 [DropDownList1] 的 [AutoPostBack] 属性已被设为 true。这意味着,一旦浏览器检测到下拉列表中的“所选项更改”事件,就会立即提交表单。 服务器则会通过 [VIEWSTATE] 及提交的值,检测到 [DropDownList1] 组件中选中的元素已发生变化。随后,它将触发该组件上的 [SelectedIndexChanged] 事件。我们将通过以下过程处理该事件:
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' 更改选择
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
当此过程执行时,对象 [DropDownList1] 通过 [VIEWSTATE] 找回了其 [ListItem] 类型的元素。 此外,其中一个类型为 [ListItem] 的元素,其 [Selected] 属性被设置为 true,该属性的值是由浏览器提交的。可以通过多种方式访问该元素:
是列表中第一个 [ListItem] 元素,其 [Selected] 属性值为 true | |
对应于用户选中的 <option value="...">文本</option> 元素中 HTML 标签的 [texte] 部分 | |
对应于用户选中的 <option value="...">文本</option> 元素中 HTML 标签的 [value] 部分 | |
在集合 [DropDownList1.Items] 中,第一个元素 [ListItem] 的 [Selected] 属性为 true 时的编号 |
[form6.aspx]的最终代码如下:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' 如果是首次被调用,则填写下拉菜单
if not IsPostBack then
dim valeurs() as String = {"1","2","3","4"}
dim textes() as String = {"un","deux","trois","quatre"}
dim i as integer
for i=0 to valeurs.length-1
DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
next
end if
end sub
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' 更改选择
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
</p>
<p>
<asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
</p>
</form>
</body>
</html>
7.8. 组件 ListBox
<asp:ListBox> 标签可用于在页面的呈现代码中插入列表。我们创建 [form7.aspx] 以获得以下呈现效果:

编号 | 名称 | 类型 | 属性 | 角色 |
1 | TextBox | EnableViewState=false | 输入框 | |
2 | 按钮 | 按钮 [submit],若 txtSaisie 不为空,则将其内容传输至列表 1。 | ||
3 | ListBox | EnableViewState=true SelectionMode=Single | 单选值列表 | |
4 | ListBox | EnableViewState=true SelectionMode=Multiple | 多选值列表 | |
5 | 按钮 | 按钮 [submit],用于将 [liste 1] 中选中的元素传输至 [liste 2] | ||
6 | Button | 按钮 [submit],将 [liste 2] 中选中的元素传输到 [liste 1] |
生成的呈现代码如下:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
Tapez un texte pour l'inclure dans Liste 1 :
<asp:TextBox id="txtSaisie" runat="server" EnableViewState="False"></asp:TextBox>
</p>
<p>
<asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter"></asp:Button>
</p>
<p>
<table>
<tbody>
<tr>
<td>
<p align="center">
Liste 1
</p>
</td>
<td>
</td>
<td>
<p align="center">
Liste 2
</p>
</td>
</tr>
<tr>
<td>
<asp:ListBox id="ListBox1" runat="server"></asp:ListBox>
</td>
<td>
<p>
<asp:Button id="btn1vers2" onclick="btn1vers2_Click" runat="server" Text="-->"></asp:Button>
</p>
<p>
<asp:Button id="btn2vers1" onclick="btn2vers1_Click" runat="server" Text="<--"></asp:Button>
</p>
</td>
<td>
<p>
<asp:ListBox id="ListBox2" runat="server" SelectionMode="Multiple"></asp:ListBox>
</p>
</td>
</tr>
<tr>
<td>
<p align="center">
<asp:Button id="btnRaz1" onclick="btnRaz1_Click" runat="server" Text="Effacer"></asp:Button>
</p>
</td>
<td>
</td>
<td>
<p align="center">
<asp:Button id="btnRaz2" onclick="btnRaz2_Click" runat="server" Text="Effacer"></asp:Button>
</p>
</td>
</tr>
</tbody>
</table>
</p>
</form>
</body>
</html>
类 [ListBox] 继承自与之前研究的类 [DropDownList] 相同的类 [ListControl]。 其中包含 [DropDownList] 类中所有已见属性和方法,因为它们实际上属于 [ListControl] 类。出现了一个新属性:
用于设置由该组件生成的列表 HTML <select> 的选择模式。若 SelectionMode=Single,则只能选择一个元素;若 SelectionMode=Multiple,则可选择多个元素。 为此,将在列表 HTML 的 <select> 标签中生成 [multiple="multiple"] 属性。 |
处理事件。点击按钮 [Ajouter] 将由以下过程 [btnAjouter_Click] 处理:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' 添加到列表1
dim texte as string=txtSaisie.text.trim
if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
' 清空 txtSaisie
txtSaisie.text=""
End Sub
如果输入到 [txtSaisie] 中的文本不是空字符串或空白字符串,则会在列表 [ListBox1] 中添加一个新元素。我们知道需要添加一个类型为 [ListItem] 的元素。 此前,我们曾使用构造函数 [ListItem(T as String, V as String)] 完成过类似的工作。此类元素会生成标签 HTML [<option value="V">T</option>]。 在此,我们使用构造函数 [ListItem(T as String)],它会生成标签 HTML、[<option value="T">T</option>] 和 c.a.d。 选项中的文本 [T] 被用于构成该选项的值。一旦 [txtSaisie] 的内容被添加到列表 [ListBox1] 中,字段 [txTSaisie] 就会被清空。
对 [Effacer] 按钮的点击将由以下过程处理:
Sub btnRaz1_Click(sender As Object, e As EventArgs)
' 清空列表1
ListBox1.Items.Clear
End Sub
Sub btnRaz2_Click(sender As Object, e As EventArgs)
' 清空列表 2
ListBox2.Items.Clear
End Sub
列表间切换按钮的点击操作由以下过程处理:
Sub btn1vers2_Click(sender As Object, e As EventArgs)
' 将列表 1 中选中的项目转移到列表 2
transfert(ListBox1,ListBox2)
End Sub
Sub btn2vers1_Click(sender As Object, e As EventArgs)
' 将列表 2 中选中的项目转移到列表 1
transfert(ListBox2,ListBox1)
End Sub
sub transfert(l1 as listbox, l2 as listbox)
' 将列表1中选中的项目转移到列表2
' 有什么需要做的吗?
if l1.selectedindex=-1 then return
dim i as integer
' 从结尾开始
for i=l1.items.count-1 to 0 step -1
' 已选中?
if l1.items(i).selected then
' 不再选中
l1.items(i).selected=false
' 转移到 l2
l2.items.add(l1.items(i))
' 从l1中删除
l1.items.removeAt(i)
end if
next
end sub
由于这两个按钮的功能相同,即把列表中的元素转移到另一个列表,因此可以将其简化为一个带有两个参数的转移过程:
- 类型为 [ListBox] 的 l1,即源列表
- l2 类型为 [ListBox],即目标列表
首先,需验证列表 l1 中是否至少有一个选中项,否则无需执行任何操作。为此,需检查属性 [l1.selectedindex],该属性表示列表中第一个选中项的序号。 若未选中任何元素,其值为 -1。若 l1 中至少有一个选中项,则执行向 l2 的转移。为此,遍历 l1 中的所有元素,并检查每个元素的 [selected] 属性是否为真。 如果是,则将其属性 [selected] 设置为 [false],随后将其复制到列表 l2 中,最后从列表 l1 中删除。此删除操作将导致列表 l1 中元素的重新编号。 这就是为什么需要反向遍历 l1 列表的原因。如果按正向遍历并删除第 10 个元素,第 11 个元素将变为第 10 个,而第 12 个元素则变为第 11 个。 处理完第10个元素后,我们的正向循环将处理第11个元素——根据上述解释,它实际上是原来的第12个元素。而原本编号为11、现在编号为10的元素则会被遗漏。通过反向遍历列表l1的元素,可以避免这一问题。
7.9. 组件 CheckBox、RadioButton
标签 <asp:RadioButton> 和 <asp:CheckBox> 分别用于在页面的呈现代码中插入单选按钮和复选框。我们创建一个名为 [form8.aspx] 的页面,以获得以下呈现效果:

编号 | 名称 | 类型 | 属性 | 角色 |
1 | RadioButton | RadioButton1.Checked=true RadioButton1.Text=1 RadioButton2.Checked=false RadioButton2.Text=2 RadioButton3.Checked=false RadioButton3.Text=3 针对这3个按钮:GroupName=radio | 单选按钮 | |
2 | CheckBox | 所有条目的Checked=false CheckBoxA.Text=A CheckBoxB.Text=BCheckBoxC.Text=C | 复选框 | |
3 | 按钮 | 按钮 [submit] | ||
4 | ListBox | 信息列表 |
为了让浏览器将这三个单选按钮视为互斥的按钮,必须将它们组合到一个单选按钮组中。这可以通过 [RadioButton] 类的 [GroupName] 属性来实现。 本应用程序无需维护页面状态。因此,我们在页面上设置了 [EnableViewState="false"] 属性。呈现代码如下:
<html>
<head>
</head>
<body>
<form id="frmControls" runat="server">
<h3>Cases à cocher
</h3>
<p>
<asp:RadioButton id="RadioButton1" runat="server" Checked="True" EnableViewState="False" GroupName="radio" Text="1"></asp:RadioButton>
<asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" GroupName="radio" Text="2"></asp:RadioButton>
<asp:RadioButton id="RadioButton3" runat="server" EnableViewState="False" GroupName="radio" Text="3"></asp:RadioButton>
</p>
<p>
<asp:CheckBox id="CheckBoxA" runat="server" EnableViewState="False" Text="A"></asp:CheckBox>
<asp:CheckBox id="CheckBoxB" runat="server" EnableViewState="False" Text="B"></asp:CheckBox>
<asp:CheckBox id="CheckBoxC" runat="server" EnableViewState="False" Text="C"></asp:CheckBox>
</p>
<p>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
<asp:Button id="btnTree" onclick="btnTree_Click" runat="server" Text="Contrôles"></asp:Button>
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6" Height="131px"></asp:ListBox>
</p>
</form>
</body>
</html>
我们需要编写过程 [btnEnvoyer_Click] 来处理该按钮上的事件 [Click]。 单选按钮或复选框的状态由其属性 [Checked] 决定,该属性为布尔值,若复选框被选中则为真,否则为假。 因此,我们只需在列表 [lstInfos] 中写入六个单选按钮和复选框的 [Checked] 属性的值。由于这并没有什么特别的难度,我们稍作创新:
<script runat="server">
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' 将信息放入列表框
for each c as control in FindControl("frmControls").controls
' 该控件是否派生自 CheckBox
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
</script>
该页面可视为控件的树形结构。在本例中,我们的页面包含文本和服务器控件。文本被视为一种名为 [LiteralControl] 的特殊控件。任何文本都会生成该控件,即使是在两个控件之间的连续空格也不例外。 每个控件都有一个用于标识它的 ID 属性。在标签中显示的是 ID 属性:
若忽略 [LiteralControl] 控件,所研究的页面包含以下控件:
- [HtmlForm],即表单 [ID=frmControls]。该表单本身是一个控件容器,包含以下控件:
-- [ID=RadioButton1],类型为 [RadioButton]
-- [ID=RadioButton2],类型为 [RadioButton]
-- [ID=RadioButton3],类型为 [RadioButton]
-- [ID=CheckBoxA],类型为 [CheckBox]
-- [ID=CheckBoxA] 类型为 [CheckBox]
-- [ID= CheckBoxA] 类型为 [CheckBox]
-- [ID=btnEnvoyer] 类型为 [Button]
一个控件具有以下属性:
如果存在,则返回 [Control] 的子控件集合 | |
返回位于 [Control] 子控件树根节点处、由 ID 标识的控件。 在上例中:Page.FindControl("frmControls") 指代容器 [HtmlForm]。要访问单选按钮 [RadioButton1],需编写 Page.FindControl("frmControls").FindControl("RadioButton1") | |
[Control]的标识符 |
让我们回到 [btnEnvoyer_Click] 过程的代码:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' 将信息放入列表框
for each c as control in FindControl("frmControls").controls
' 该控件是否派生自 CheckBox?
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
我们希望显示表单中单选按钮和复选框的状态。为此,我们将遍历表单中的所有控件。如果当前控件的类型是从 [CheckBox] 派生而来的,则显示其属性 [Checked]。 由于类 [RadioButton] 继承自类 [CheckBox],因此该检测对这两种控件类型均有效。上方的屏幕截图展示了一个执行示例。
7.10. 组件 CheckBoxList、RadioButtonList
有时,我们希望引导用户在页面设计时尚未确定的值之间进行选择。这些选项来自配置文件、数据库等,且仅在运行时才确定。针对此问题存在解决方案,我们也曾遇到过。 当用户只能进行单一选择时,单选列表是理想的选择;而当用户可以进行多项选择时,则应使用多选列表。 从美学角度考虑,且选项数量不多时,可能希望使用单选按钮代替单选列表,或使用复选框代替多选列表。通过组件 [CheckBoxList] 和 [RadioButtonList] 即可实现。
类 [CheckBoxList] 和 [RadioButtonList] 继承自与之前探讨的类 [DropDownList] 和 [ListBox] 相同的基类 [ListControl]。 因此,我们将看到这些类中出现过的一些属性与方法,这些实际上原本属于 [ListControl] 类。
下拉列表项的 [ListItemCollection] 类型集合。该集合的成员类型为 [ListItem]。 | |
集合中元素的数量 [Items] | |
列表中的第 i 个元素 - 类型为 [ListItem] | |
向集合 [Items] 中添加新元素 [ListItem] | |
以删除集合 [Items] 中的所有元素 | |
用于从集合 [Items] 中删除第 i 个元素 | |
集合 [Items] 中第 1 个元素 [ListItem],其属性 [Selected] 为真 | |
集合中前一个元素的编号 [Items] |
某些属性仅适用于类 [CheckBoxList] 和 [RadioButtonList]:
[horizontal] 或 [vertical] 用于水平或垂直列表。 |
集合 [Items] 中的元素属于 [ListItem] 类型。 每个 [ListItem] 元素将生成不同的标签,具体取决于其对应的对象是 [CheckBoxList] 还是 [RadioButtonList]:
或
我们描述 [ListItem] 类的某些属性和方法:
构造函数 - 创建一个具有 text 和 value 属性的 [ListItem] 元素。 一个 ListItem(T,V) 元素将生成相应的 HTML 标签 <input type="checkbox" value="V">T 或 <input type="radio" value="V">T,具体取决于情况。 | |
布尔值。若为真,则列表 HTML 中对应的选项将具有属性 [selected="selected"]。该属性指示浏览器,在列表 HTML 中,对应的元素应显示为已选中 | |
选项 HTML 中的文本 T <input type=".." value="V" [selected="selected"]>T | |
选项 HTML 的 Value 属性的值 <input type=".." value="V" [selected="selected"]>T |
我们计划构建以下页面:

编号 | 名称 | 类型 | 属性 | 角色 |
1 | RadioButtonList | EnableViewState=true RepeatDirection=horizontal | 单选按钮列表 | |
2 | CheckBoxList | EnableViewState=true RepeatDirection=horizontal | 复选框列表 | |
3 | 按钮 | 按钮 [submit],用于在 [4] 中显示两个列表中选中的项目列表 | ||
4 | ListBox | EnableViewState=false | 值列表 |
该页面的呈现代码如下:
<%@ Page Language="VB" autoeventwireup="false" %>
<script runat="server">
...
</script>
<html>
<head>
</head>
<body>
<form id="frmControls" runat="server">
<h3>Listes de cases à cocher
</h3>
<p>
<asp:RadioButtonList id="RadioButtonList1" runat="server" RepeatDirection="Horizontal"></asp:RadioButtonList>
</p>
<p>
<asp:CheckBoxList id="CheckBoxList1" runat="server" RepeatDirection="Horizontal"></asp:CheckBoxList>
</p>
<p>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6"></asp:ListBox>
</p>
</form>
</body>
</html>
校验码如下:
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
' 如果是首次调用,则填充列表
if not IsPostBack then
' 列表文本 RadioButton
dim textesRadio() as String = {"1","2","3","4"}
' 列表文本 CheckBox
dim textesCheckBox() as String = {"un","deux","trois","quatre"}
' 单选列表填充
dim i as integer
for i=0 to textesRadio.length-1
RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
next
' 选择第 1 项
RadioButtonList1.SelectedIndex=1
' 填充复选框列表
for i=0 to textesCheckBox.length-1
CheckBoxList1.Items.Add(new ListItem(textesCheckBox(i)))
next
end if
end sub
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' 将信息放入 lstinfos 列表框
affiche(RadioButtonList1)
affiche(CheckBoxList1)
End Sub
sub affiche(l1 as ListControl)
' 显示 l1 中所选项的值
' 需要做些什么?
if l1.selectedindex=-1 then return
dim i as integer
' 从结尾开始
for i= 0 to l1.items.count-1
' 已选中?
if l1.items(i).selected then
lstInfos.Items.Add("["+TypeName(l1)+"] ["+l1.items(i).text+"] sélectionné")
end if
next
end sub
</script>
在每次页面执行时都会运行的 [Page_Load] 流程中,这两个列表会被初始化。为了避免每次都进行初始化,我们使用 [IsPostBack] 属性,使其仅在首次执行时进行初始化。 后续访问时,列表将通过 [VIEWSTATE] 机制自动刷新。页面显示后,用户勾选某些复选框并点击 [Envoyer] 按钮,表单数据随即提交至表单本身。 在执行 [Page_Load] 之后,将执行 [btnEnvoyer_Click] 过程。该过程调用 [affiche] 过程来填充列表 [lstInfos]。 该列表接收一个类型为 [ListControl] 的对象作为参数,因此可以向其发送 [RadioButtonList] 对象或 [CheckBoxList] 对象,这两者都是 [ListControl] 的派生类。 列表 [lstInfos] 的 [EnableViewState] 属性可以设置为 [false],因为其状态无需在不同请求之间保持。
7.11. Panel 组件,LinkButton
<asp:panel> 标签用于在页面中插入一个控件容器。该容器的优势在于,其某些属性适用于其中包含的所有控件。其 [Visible] 属性便是如此。该属性适用于所有服务器控件。 如果容器具有 [Visible=false] 属性,则其中的每个控件将由其自身的 [Visible] 属性进行控制。 如果它具有属性 [Visible=false],则该容器及其所有内容均不会显示。这可能比管理容器中每个控件的 [Visible] 属性更为简便。
<asp:LinkButton> 标签可用于在页面的呈现代码中插入链接。 其作用类似于 [Button] 按钮。实际上,它通过关联的 JavaScript 函数在客户端触发 POST 事件。我们创建一个 [form9.aspx] 页面以获得以下呈现效果:

编号 | 名称 | 类型 | 属性 | 角色 |
1 | 面板 | EnableViewState=true | 控件容器 | |
2 | ListBox | EnableViewState=true | 包含三个值的列表 | |
3 | LinkButton | EnableViewState=false | 隐藏容器的链接 |
当容器被隐藏时,会出现一个新链接:

编号 | 名称 | 类型 | 属性 | 角色 |
4 | LinkButton | EnableViewState=false | 显示容器的链接 |
该页面的呈现代码如下:
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:Panel id="Panel1" runat="server" BorderStyle="Ridge" BorderWidth="1px">
<p>
Conteneur
</p>
<p>
<asp:ListBox id="ListBox1" runat="server">
<asp:ListItem Value="1">un</asp:ListItem>
<asp:ListItem Value="2">deux</asp:ListItem>
<asp:ListItem Value="3" Selected="True">trois</asp:ListItem>
</asp:ListBox>
</p>
</asp:Panel>
</p>
<p>
<asp:LinkButton id="lnkVoir" onclick="lnkVoir_Click" runat="server">Voir le conteneur</asp:LinkButton>
</p>
<p>
<asp:LinkButton id="lnkCacher" onclick="lnkCacher_Click" runat="server">Cacher le conteneur</asp:LinkButton>
</p>
</form>
</body>
</html>
请注意,此代码为列表 [ListBox1] 初始化了三个值。两个链接上的 [Clic] 事件处理程序如下:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
...
end sub
Sub lnkVoir_Click(sender As Object, e As EventArgs)
' 显示容器 1
panel1.Visible=true
' 更改链接
lnkVoir.visible=false
lnkCacher.visible=true
End Sub
Sub lnkCacher_Click(sender As Object, e As EventArgs)
' 隐藏容器 1
panel1.Visible=false
' 更改链接
lnkVoir.visible=true
lnkCacher.visible=false
End Sub
</script>
我们将使用过程 [Page_Load] 来初始化表单。这将在首次请求时执行(IsPostBack=false):
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' 第一次
if not IsPostBack then
' 显示容器
lnkVoir_Click(nothing,nothing)
end if
end sub
.....
</script>
7.12. 继续阅读……
前面的段落介绍了一些服务器组件。每次仅介绍了其中的一些属性。为了更深入地研究这些组件,读者可以采取以下几种方法:
- 使用如 WebMatrix 这样的 IDE 文件来探索某个组件的属性。该文件实际上展示了表单中使用的组件的主要属性
- 查阅 .NET 的文档,以了解与每个服务器组件相对应的完整类。这是全面掌握组件的首选方法。您将在此发现通向各组件的类树结构,以及每个类的属性、方法、构造函数和事件。此外,文档中有时还会提供示例。
在本章中,我们采用了“全合一”技术([WebMatrix]),即将页面的呈现代码和控制代码置于同一个文件中。 总体而言,我们并不推荐这种方法,而是推荐之前使用的所谓 [codebehind] 方法,该方法将这两部分代码分别放在两个独立的文件中。需要重申的是,这种分离的优势在于,控制代码可以在无需运行 Web 应用程序的情况下进行编译。此外, 正如本章开头所述,我们的示例具有非常特殊的特征:它们由单个页面组成,该页面是一个表单,客户端与服务器通过连续的请求-响应循环进行交互,其中客户端的第一个请求是 GET,后续请求则是 POST。
7.13. 服务器组件与应用程序控制器
在前几章中,我们构建了多个 Web 应用程序。它们均遵循 MVC(模型-视图-控制器)架构,该架构将应用程序划分为清晰独立的模块,从而简化了维护工作。 当时我们使用标准的HTML标签构建用户界面。基于前文所学,现在自然会想要使用服务器组件。让我们重新审视一个已经深入探讨过的问题——税款计算。其MVC架构如下:

该应用程序包含两个视图:[formulaire.aspx] 和 [erreurs.aspx]。当首次请求 URL [main.aspx] 时,将显示视图 [formulaire.aspx]:

用户填写表单:

并点击按钮 [Calculer] 获得以下响应:

在应用程序 MVC 中,所有请求都必须经过控制器,此处为 [main.aspx]。 这意味着当用户填写完表单 [formulaire.aspx] 后,应将其提交至 [main.aspx],而非 [formulaire.aspx]。 如果我们使用服务器端组件 ASP 来构建用户界面 [formulaire.aspx],这将完全无法实现。为了验证这一点,让我们构建一个包含 <asp:button> 组件的表单 [formtest.aspx]:
<%@ Page Language="VB" EnableViewState="false"%>
<html>
<head>
<title>test</title>
</head>
<body>
<form action="main.aspx" runat="server">
<p>
<asp:Button id="btnTest" runat="server" EnableViewState="false" Text="Test"></asp:Button>
</p>
</form>
</body>
</html>
请注意<form..>标签的[action="main.aspx"]属性。让我们运行这个应用程序。展示页面上仅有一个按钮:

让我们看看服务器发送的代码 HTML:
<html>
<head>
<title>test</title>
</head>
<body>
<form name="_ctl0" method="post" action="formtest.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwtNTMwNzcxMzI0Ozs+" />
<p>
<input type="submit" name="btnTest" value="Test" id="btnTest" />
</p>
</form>
</body>
</html>
可以看出,表单中的 POST 指向的是表单本身 [action="formtest.aspx"],而我们在 [formtest.aspx] 中写入的标签 HTML 服务器:
由于使用了服务器组件,<form> 标签的 [runat="server"] 属性是强制要求的。如果不添加该属性,将会引发编译错误。当添加该属性时,<form> 标签的 [action] 属性将被忽略。 服务器始终会生成一个指向表单本身的 [action] 属性。 由此可知,在 MVC 应用程序中,无法使用通过 <form ... runat="server"> 标签构建的表单。然而,该标签对于所有用于获取用户输入的 ASP 服务器组件而言都是必不可少的。 这无异于说,在 MVC 应用程序中无法使用 ASP 服务器表单。这真是一项重大发现。事实上,ASP.NET 营销宣传的一大亮点,正是能够像构建 Windows 应用程序一样构建 Web 应用程序。 如果我们的应用程序不遵循MVC架构,这一点确实成立;但反之则不然。然而,MVC架构似乎是当今Web开发的一个基本概念,很难被忽视。
对于视图较少的应用程序,可以通过以下技巧将 MVC 架构与 ASP 组件表单结合使用:
- 应用程序由一个充当控制器的单一页面组成
- 视图在该页面中通过不同的容器实现,每个视图对应一个容器。要显示某个视图,只需将其容器设为可见,同时隐藏其他容器
这是一个优雅的解决方案,我们现在将通过几个示例来实现它
7.14. MVC 应用程序示例(含 ASP 服务器组件)
7.14.1. 示例 1
在这个第一个示例中,我们将实现之前介绍过的服务器组件。页面 [form10.aspx] 将如下所示:
![]() | ![]() |
上图左侧的屏幕截图显示的是客户看到的表单。客户填写表单后,通过[Envoyer]进行提交。 服务器向其返回一个页面,展示所输入值的列表(右侧截图)。通过一个链接,用户可以返回表单。此时表单显示状态与提交时一致。[form10.aspx]的呈现代码如下:
<html>
<head>
<title>Exemple</title> <script language="javascript">
function effacer(){
alert("Vous avez cliqué sur [Effacer]")
}
</script>
</head>
<body>
<p>
Gestion d'un formulaire
</p>
<p>
<hr />
</p>
<form runat="server">
<p>
<asp:Panel id="panelinfo" runat="server" EnableViewState="False">
<p>
Liste des valeurs obtenues
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False"></asp:ListBox>
</p>
<p>
<asp:LinkButton id="LinkButton1" onclick="LinkButton1_Click" runat="server">Retour au formulaire</asp:LinkButton>
</p>
<p>
<hr />
</p>
</asp:Panel>
</p>
<p>
<asp:Panel id="panelform" runat="server" >
<table>
<tbody>
<tr>
<td>
Etes-vous marié(e)</td>
<td>
<asp:RadioButton id="rdOui" runat="server" GroupName="rdmarie"></asp:RadioButton>
Oui<asp:RadioButton id="rdNon" runat="server" GroupName="rdmarie" Checked="True"></asp:RadioButton>
Non</td>
</tr>
<tr>
<td>
Cases à cocher</td>
<td>
<asp:CheckBox id="chk1" runat="server"></asp:CheckBox>
1<asp:CheckBox id="chk2" runat="server"></asp:CheckBox>
2<asp:CheckBox id="chk3" runat="server"></asp:CheckBox>
3</td>
</tr>
<tr>
<td>
Champ de saisie</td>
<td>
<asp:TextBox id="txtSaisie" runat="server" MaxLength="20" Columns="20"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Mot de passe</td>
<td>
<asp:TextBox id="txtmdp" runat="server" MaxLength="10" Columns="10" TextMode="Password"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Boîte de saisie</td>
<td>
<asp:TextBox id="txtArea" runat="server" Columns="20" TextMode="MultiLine" Rows="3"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Liste déroulante</td>
<td>
<asp:DropDownList id="cmbValeurs" runat="server"></asp:DropDownList>
</td>
</tr>
<tr>
<td>
Liste à choix unique</td>
<td>
<asp:ListBox id="lstSimple" runat="server"></asp:ListBox>
<asp:Button id="btnRazSimple" onclick="btnRazSimple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
</td>
</tr>
<tr>
<td>
Liste à choix multiple</td>
<td>
<asp:ListBox id="lstMultiple" runat="server" SelectionMode="Multiple"></asp:ListBox>
<asp:Button id="razMultiple" onclick="razMultiple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
</td>
</tr>
<tr>
<td>
Champ caché</td>
<td>
<asp:Label id="lblSecret" runat="server" visible="False"></asp:Label></td>
</tr>
<tr>
<td>
Bouton simple</td>
<td>
<input id="btnEffacer" onclick="effacer()" type="button" value="Effacer" /></td>
</tr>
<tr>
<td>
Bouton [reset]</td>
<td>
<input id="btnReset" type="reset" value="Rétablir" /></td>
</tr>
<tr>
<td>
Bouton [submit]</td>
<td>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" EnableViewState="False" Text="Envoyer"></asp:Button>
</td>
</tr>
</tbody>
</table>
</asp:Panel>
</p>
</form>
</body>
</html>
该页面包含两个容器,每个视图各有一个:[panelform] 用于表单视图,[panelinfo] 用于信息视图。[panelForm] 容器的组件列表如下:
名称 | 类型 | 属性 | 角色 |
面板 | EnableViewState=true | 表单视图 | |
RadioButton | EnableViewState=true GroupName=rdmarie | 单选按钮 | |
CheckBox | EnableViewState=true | 复选框 | |
TextBox | EnableViewState=true | 输入框 | |
TextBox | EnableViewState=true | 受保护的输入字段 | |
TextBox | EnableViewState=true | 多行输入框 | |
DropDownList | EnableViewState=true | 下拉列表 | |
ListBox | EnableViewState=true SelectionMode=Single | 单选列表 | |
Button | EnableViewState=false | 取消选择 lstSimple 中的所有项目 | |
ListBox | EnableViewState=true SelectionMode=Multiple | 多选列表 | |
按钮 | EnableViewState=false | 取消选择 lstMultiple 中的所有项目 | |
标签 | EnableViewState=true Visible=false | 隐藏字段 | |
HTML 标准 | 显示警报 | ||
按钮 | EnableViewState=false | 表单中的 [submit] 按钮 | |
HTML 标准 | 表单中的按钮 [reset] |
[VIEWSTATE] 在组件中的作用至关重要。除按钮外,所有组件都必须具有 [EnableViewState=true] 属性。 要理解其原因,需回顾应用程序的工作原理。假设字段 [txtSaisie] 具有属性 [EnableViewState=false]:
- 客户首次请求页面 [form10.aspx]。他获得表单视图
- 用户填写表单后,通过按钮 [Envoyer] 提交。 此时,输入字段的数据被提交,服务器将提交的值分配给服务器组件,或者如果它们有状态,则分配其状态 [VIEWSTATE]。因此,字段 [txtSaisie] 被分配了用户输入的值。 因此,在此步骤中,其状态 [VIEWSTATE] 没有任何作用。操作结果是发送视图 [informations],实际上仍是页面 [form10.aspx],但显示的容器不同。
- 用户查看此新视图,并使用链接 [Retour au formulaire] 返回该视图。随后会从 POST 跳转至 [form10.aspx]。 此时最多只有一个提交的值:即用户在信息列表中选中的值,该信息后续未被利用。无论如何,都没有提交 [txtSaisie] 字段。
- 服务器接收 POST,并将提交的值分配给服务器组件,或者如果它们有状态,则分配其状态 [VIEWSTATE]。在此,[txtNom] 没有提交的值。 如果其属性 [EnableViewState] 的值为 [false],则将为其分配空字符串。由于希望其取用户输入的值,因此必须设置 [EnableViewState=true] 属性。
容器 [panelinfo] 包含以下控件:
名称 | 类型 | 属性 | 角色 |
面板 | EnableViewState=false | 信息视图 | |
ListBox | EnableViewState=false | 汇总用户输入值的信息列表 | |
LinkButton | EnableViewState=false | 返回表单的链接 |
在测试时,如果查看由上述呈现代码生成的 HTML 代码,可能会对隐藏字段 [lblSecret] 生成的代码感到惊讶:
组件 [lblSecret] 未转换为代码 HTML,因为它具有属性 [Visible=false]。 然而,由于它具有属性 [EnableViewState=true],其值仍将保存在隐藏字段 [__VIEWSATE] 中。因此,正如测试所示,我们将能够检索该值。
接下来我们需要编写事件处理程序。在 [Page_Load] 中,我们将初始化表单:
Sub page_Load(sender As Object, e As EventArgs)
' 第一次,初始化元素
' 后续执行时,这些元素通过 VIEWSTATE 恢复其值
if IsPostBack then return
' 初始化表单
' 未显示面板信息
panelinfo.visible=false
' 显示表单面板
panelform.visible=true
' 单选按钮
rdNon.Checked=true
' 复选框
chk2.Checked=true
' 输入框
txtSaisie.Text="qqs mots"
' 密码字段
txtMdp.Text="ceciestsecret"
' 输入框
txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
' 下拉列表
dim i as integer
for i=1 to 4
cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
next
cmbValeurs.SelectedIndex=1
' 单选列表
for i=1 to 7
lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
next
lstSimple.SelectedIndex=0
' 多选列表
for i=1 to 10
lstMultiple.Items.Add(new ListItem("multiple"+i.ToString,i.ToString))
next
lstMultiple.Items(0).Selected=true
lstMultiple.Items(2).Selected=true
' 隐藏字段
lblSecret.Text="secret"
End Sub
点击按钮 [lstRazSimple] 和 [lstMultiple]:
Sub btnRazSimple_Click(sender As Object, e As EventArgs)
' 单选列表清空
lstSimple.SelectedIndex=-1
End Sub
Sub razMultiple_Click(sender As Object, e As EventArgs)
' 多选列表清空
lstMultiple.SelectedIndex=-1
End Sub
点击按钮 [Envoyer]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' 显示信息面板并隐藏表单面板
panelinfo.Visible=true
panelform.visible=false
' 获取提交的值并将其放入 lstInfos
' 单选按钮
dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
affiche(info)
' 复选框
info=" cases cochées : "+iif(chk1.checked,"1 oui","1 non")+","+ _
iif(chk2.checked,"2 oui","2 non")+","+iif(chk3.checked,"3 oui","3 non")
affiche(info)
' 输入框
affiche("champ de saisie : " + txtSaisie.Text.Trim)
' 密码
affiche("mot de passe : " + txtMdp.Text.Trim)
' 输入框
dim lignes() as String
lignes=new Regex("\r\n").Split(txtArea.Text.Trim)
dim i as integer
for i=0 to lignes.length-1
lignes(i)="["+lignes(i).Trim+"]"
next
affiche("Boîte de saisie : " + String.Join(",",lignes))
' 下拉列表
affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
' 单选列表
affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
' 多选列表
affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
' 隐藏字段
affiche ("Champ caché : " + lblSecret.Text)
End Sub
sub affiche(msg as String)
' 在 lstInfos 中显示消息
lstInfos.Items.Add(msg)
end sub
function selection(liste as ListControl) as string
' 遍历列表项
' 以查找已选中的项目
dim i as integer
dim info as string=""
for i=0 to liste.Items.Count-1
if liste.Items(i).Selected then info+="[" + liste.Items(i).Text + "]"
next
return info
end function
最后点击链接 [Retour vers le formulaire ]:
Sub LinkButton1_Click(sender As Object, e As EventArgs)
' 显示表单并隐藏信息面板
panelform.visible=true
panelinfo.visible=false
End Sub
7.14.2. 示例 2
这里我们重新探讨一个已使用标准表单 HTML 处理过的应用程序。该应用程序可用于进行税款计算模拟。它基于类 [impot],此处不再赘述。 该类需要从数据源 OLEDB 中获取数据。在本示例中,将使用数据源 ACCESS。
7.14.2.1. 应用程序的 MVC 结构
该应用程序的 MVC 结构如下:

这三个视图将作为容器嵌入到控制器 [main.aspx] 的呈现代码中。因此,该应用程序仅有一个页面 [main.aspx]。
7.14.2.2. Web 应用程序的视图
视图 [formulaire] 是用于输入信息以计算用户税款的表单:

用户填写表单:

他点击按钮 [Envoyer] 请求计算税款。随后将显示以下视图 [simulations]:

用户通过上面的链接返回表单。表单内容保持其输入时的状态。用户可能出现输入错误:

系统将通过视图 [erreurs] 提示这些错误:

用户通过上面的链接返回表单。表单将保持其输入时的状态。用户可以进行新的模拟:

随后将显示视图 [simulations],其中包含一项新的模拟结果:

最后,如果数据源不可用,系统会在视图 [erreurs] 中向用户发出提示:

7.14.2.3. 应用程序的呈现代码
需要提醒的是,页面 [main.aspx] 汇集了所有视图。这是一个包含三个容器的单一表单:
- [panelform] 用于视图 [formulaire]
- [panelerreurs] 用于视图 [erreurs]
- [panelsimulations] 用于视图 [simulations]
我们重新采用将呈现代码与控制代码分离到两个独立文件中的做法。前者位于 [main.aspx],后者位于 [main.aspx.vb]。[main.aspx] 的代码如下:
<%@ page codebehind="main.aspx.vb" inherits="vs.main" AutoEventWireUp="false" %>
<HTML>
<HEAD>
<title>Calcul d'impôt </title>
</HEAD>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<FORM id="Form1" runat="server">
<asp:panel id="panelform" Runat="server">
<TABLE id="Table1" cellSpacing="1" cellPadding="1" border="0">
<TR>
<TD height="19">Etes-vous marié(e)</TD>
<TD height="19">
<asp:RadioButton id="rdOui" runat="server" GroupName="rdMarie"></asp:RadioButton>Oui
<asp:RadioButton id="rdNon" runat="server" GroupName="rdMarie" Checked="True"></asp:RadioButton>Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD>
<asp:TextBox id="txtEnfants" runat="server" MaxLength="3" Columns="3"></asp:TextBox></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD>
<asp:TextBox id="txtSalaire" runat="server" MaxLength="10" Columns="10"></asp:TextBox></TD>
</TR>
</TABLE>
<P>
<asp:Button id="btnCalculer" runat="server" Text="Calculer"></asp:Button>
<asp:Button id="btnEffacer" runat="server" Text="Effacer"></asp:Button></P>
</asp:panel>
<asp:panel id="panelerreurs" runat="server">
<P>Les erreurs suivantes se sont produites :</P>
<P>
<asp:Literal id="erreursHTML" runat="server"></asp:Literal></P>
<P></P>
<asp:LinkButton id="lnkForm1" runat="server">Retour au formulaire</asp:LinkButton>
</asp:panel>
<asp:panel id="panelsimulations" runat="server">
<P>
<TABLE>
<TR>
<TH>
Marié</TH>
<TH>
Enfants</TH>
<TH>
Salaire annuel</TH>
<TH>
Impôt à payer (euro)</TH></TR>
<asp:Literal id="simulationsHTML" runat="server"></asp:Literal></TABLE>
<asp:LinkButton id="lnkForm2" runat="server">Retour au formulaire</asp:LinkButton></P>
</asp:panel>
</FORM>
</body>
</HTML>
我们已经界定了这三个容器。需要注意的是,它们都位于 <form runat="server"> 标签内。这是必须的,因为如果想要利用服务器组件的优势,就必须将它们放置在这样的标签中。需要重点理解的是,这里有一个唯一的表单,客户端和Web服务器将通过它进行交互。 因此,这完全符合本章关于服务器组件的配置要求。下面详细说明每个容器的组件:
容器 [panelform]:
名称 | 类型 | 属性 | 角色 |
面板 | EnableViewState=true | 表单视图 | |
RadioButton | EnableViewState=true GroupName=rdmarie | 单选按钮 | |
TextBox | EnableViewState=true | 子项数量 | |
TextBox | EnableViewState=true | 年薪 | |
按钮 | 表单按钮 [submit] - 启动税款计算 | ||
Button | 表单按钮 [submit] - 清空表单 |
容器 [panelerreurs]:
名称 | 类型 | 属性 | 角色 |
面板 | EnableViewState=true | 错误视图 | |
LinkButton | EnableViewState=true | 表单链接 | |
字面量 | 错误列表中的代码 HTML |
容器 [panelsimulations]:
名称 | 类型 | 属性 | 角色 |
面板 | EnableViewState=true | 视图模拟 | |
LinkButton | EnableViewState=true | 表单链接 | |
字面值 | HTML 表中模拟列表的代码HTML |
7.14.2.4. 应用程序的控制代码
应用程序的控制代码分布在文件 [global.asax.vb] 和 [main.aspx.vb] 中。文件 [global.asax] 的定义如下:
文件 [global.asax.vb] 如下所示:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' 创建一个导入对象
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' 将对象放入应用程序
Application("objImpot") = objImpot
' 无错误
Application("erreur") = False
Catch ex As Exception
'发生错误,在应用程序中记录
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' 会话开始 - 创建一个空的模拟列表
Session.Item("simulations") = New ArrayList
End Sub
End Class
当应用程序启动时(向应用程序发出的第一个请求),将执行过程 [Application_Start]。该过程试图创建一个类型为 [impot] 的对象,其数据来源于源 OLEDB。 若读者已忘记该类,请参阅第 5 章,该类已在其中定义。如果数据源不可用,[impot] 对象的创建可能会失败。 在此情况下,错误信息将存储在应用程序中,以便后续所有查询都能知晓该对象未能正确初始化。若创建成功,生成的 [impot] 对象也会存储在应用程序中。该对象将用于所有税款计算查询。 当客户发起首次请求时,[Application_Start] 过程会为其创建一个会话。该会话用于存储客户后续进行的各类税款计算模拟。这些模拟将存储在与会话键“simulations”关联的 [ArrayList] 对象中。 会话启动时,该键将关联到一个空的 [ArrayList] 对象。应用程序所需的信息存储在其配置文件 [wenConfig] 中:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="chaineConnexion" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\webforms\vs\impots5\impots.mdb" />
</appSettings>
</configuration>
密钥 [chaineConnexion] 指代源 OLEDB 的连接字符串。控制码的另一部分位于 [main.aspx.vb] 中:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports st.istia.univangers.fr
Imports System
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents rdOui As System.Web.UI.WebControls.RadioButton
Protected WithEvents rdNon As System.Web.UI.WebControls.RadioButton
Protected WithEvents txtEnfants As System.Web.UI.WebControls.TextBox
Protected WithEvents txtSalaire As System.Web.UI.WebControls.TextBox
Protected WithEvents btnCalculer As System.Web.UI.WebControls.Button
Protected WithEvents btnEffacer As System.Web.UI.WebControls.Button
Protected WithEvents panelform As System.Web.UI.WebControls.Panel
Protected WithEvents lnkForm1 As System.Web.UI.WebControls.LinkButton
Protected WithEvents lnkForm2 As System.Web.UI.WebControls.LinkButton
Protected WithEvents panelerreurs As System.Web.UI.WebControls.Panel
Protected WithEvents panelsimulations As System.Web.UI.WebControls.Panel
Protected WithEvents simulationsHTML As System.Web.UI.WebControls.Literal
Protected WithEvents erreursHTML As System.Web.UI.WebControls.Literal
' 局部变量
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub afficheFormulaire()
...
End Sub
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
...
End Sub
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
...
End Sub
Private Function checkData() As ArrayList
...
End Function
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
....
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
...
End Sub
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
...
End Sub
Private Sub razForm()
...
End Sub
End Class
Le premier événement traité par le code est [Page_Load] :
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' 首先,检查应用程序的状态
If CType(Application("erreur"), Boolean) Then
' 应用程序无法初始化
' 显示错误视图
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, "")
Exit Sub
End If
' 无错误——在首次请求时,显示表单
If Not IsPostBack Then afficheFormulaire()
End Sub
需要提醒的是,当在客户端上执行 [Page_Load] 过程时,表单中的所有组件都会具有一个值:要么是客户端提交的值(如果存在),要么是通过 [VIEWSTATE] 获取的该组件的先前值。 在此表单中,所有组件均具有 [EnableViewState=true] 属性。 在开始处理请求之前,我们会确保应用程序已正确初始化。如果未初始化,则通过 [afficheErreurs] 过程显示视图 [erreurs]。 如果是首次请求(IsPostBack=false),则通过 [afficheFormulaire] 显示视图 [formulaire]。
显示视图 [erreurs] 的过程如下:
Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
' 显示错误容器
panelerreurs.Visible = True
Dim i As Integer
erreursHTML.Text = ""
For i = 0 To erreurs.Count - 1
erreursHTML.Text += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
lnkForm1.Text = lien
' 隐藏其他容器
panelform.Visible = False
panelsimulations.Visible = False
End Sub
该过程有两个参数:
- [erreurs]中的错误消息列表
- [lien]中的链接文本
用于生成错误列表的代码 HTML 被放置在字面量 [erreursHTML] 中。 链接文本则被放置在视图对象 [LinkButton] 的属性 [Text] 中。
显示视图 [formulaire] 的过程如下:
Private Sub afficheFormulaire()
' 显示表单
panelform.Visible = True
' 隐藏其他容器
panelerreurs.Visible = False
panelsimulations.Visible = False
End Sub
此过程仅将容器 [panelform] 设为可见。组件将显示其已提交或之前的值(VIEWSTATE)。
当用户点击视图 [formulaire] 中的按钮 [Calculer] 时,将执行从 POST 到 [main.aspx] 的操作。 先执行过程 [Page_Load],然后执行过程 [btnCalculer_Click]:
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' 验证输入数据的有效性
Dim erreurs As ArrayList = checkData()
' 如有错误,请报告
If erreurs.Count <> 0 Then
' 显示错误页面
afficheErreurs(erreurs, "Retour au formulaire")
Exit Sub
End If
' 无错误 - 计算税款
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
' 将结果添加到现有模拟中
Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
' 将结果添加到现有模拟中
Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
simulations.Add(simulation)
' 将模拟结果放入会话和上下文中
Session.Item("simulations") = simulations
' 显示结果页面
afficheSimulations(simulations, "Retour au formulaire")
End Sub
该过程首先使用过程 [checkData] 验证表单字段的有效性,该过程会返回一个包含错误消息的列表 [ArrayList]。如果该列表不为空,则显示视图 [erreurs],并结束该过程。 如果输入的数据有效,则通过在应用程序启动时存储在其中的类型为 [impot] 的对象来计算税额。此新模拟将添加到已执行的模拟列表中,并存储在会话中。
函数 [CheckData] 会验证数据的有效性。它返回一个包含错误消息的列表 [ArrayList],若数据有效,则该列表为空:
Private Function checkData() As ArrayList
' 初始时无错误
Dim erreurs As New ArrayList
' 子女数量
Try
Dim nbEnfants As Integer = CType(txtEnfants.Text, Integer)
If nbEnfants < 0 Then Throw New Exception
Catch
erreurs.Add("Le nombre d'enfants est incorrect")
End Try
' 工资
Try
Dim salaire As Long = CType(txtSalaire.Text, Long)
If salaire < 0 Then Throw New Exception
Catch
erreurs.Add("Le salaire annuel est incorrect")
End Try
' 显示错误列表
Return erreurs
End Function
最后,视图 [simulations] 由以下过程 [afficheSimulations] 显示:
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
' 显示模拟视图
panelsimulations.Visible = True
' 隐藏其他容器
panelerreurs.Visible = False
panelform.Visible = False
' 模拟视图的内容
' 每个模拟都是一个包含 4 个字符串元素的数组
Dim simulation() As String
Dim i, j As Integer
simulationsHTML.Text = ""
For i = 0 To simulations.Count - 1
simulation = CType(simulations(i), String())
simulationsHTML.Text += "<tr>"
For j = 0 To simulation.Length - 1
simulationsHTML.Text += "<td>" + simulation(j) + "</td>"
Next
simulationsHTML.Text += "</tr>" + ControlChars.CrLf
Next
' 链接
lnkForm2.Text = lien
End Sub
该过程有两个参数:
- [simulations]中的模拟列表
- [lien]中的链接文本
用于模拟列表的代码 HTML 被放置在字面量 [simulationsHTML] 中。链接文本则被放置在视图中对象 [LinkButton] 的属性 [Text] 中。
当用户点击视图 [formulaire] 中的按钮 [Effacer] 时,将执行过程 [btnEffacer_click](始终在 [Page_Load] 之后):
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
' 显示空表单
razForm()
afficheFormulaire()
End Sub
Private Sub razForm()
' 清空表单
rdOui.Checked = False
rdNon.Checked = True
txtEnfants.Text = ""
txtSalaire.Text = ""
End Sub
上面的代码足够简单,无需注释。接下来我们需要处理对视图 [erreurs] 和 [simulations] 中链接的点击:
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
' 显示表单
afficheFormulaire()
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
' 显示表单
afficheFormulaire()
End Sub
这两个过程仅负责显示视图 [formulaire]。我们知道,该视图中的字段将获得一个值,该值要么是为其提交的值,要么是其之前的值。正如这里所示,客户端的 POST 并未为表单字段发送任何值,因此这些字段将恢复其之前的值。 因此,表单将显示用户输入的值。我们记得,在没有服务器组件的版本中,我们曾自行完成过这种恢复工作。
7.14.2.5. 测试
应用程序所需的所有文件都放置在 <application-path> 文件夹中: ![]() | 文件夹 [bin] 包含一个 DLL 文件,其中包含应用程序所需的类 [impot]、[impotsData] 和 [impotsOLEDB]: |
读者如需了解上述 [impot.dll] 文件的创建方法,可重读第 5 章。完成上述操作后,使用参数 (<application-path>,/impots5) 启动 Cassini 服务器。随后通过浏览器访问 URL [http://impots5/main.aspx]:

如果将文件 ACCESS 重命名为 [impots.mdb],然后再次重命名为 [impots1.mdb],将显示以下页面:

7.14.3. 示例 3
通过这两个示例,我们展示了如何利用服务器组件构建符合 MVC 架构的 Web 应用程序。最后一个示例表明,采用服务器组件的解决方案比使用标准 HTML 标签的解决方案更为简单。 我们的两个示例仅包含一个页面,且同一页面内包含多个视图。只要这些表单将表单值提交给自己这一操作不构成问题,就可以构建包含多个服务器端表单的MVC架构。这在带有菜单的应用程序中非常常见。 让我们来看以下示例:

我们在同一页面中汇总了迄今为止编写的所有应用程序的链接。此类应用程序非常适合采用 MVC 架构。只不过,控制器不再是单个,而是多个。

控制器[main.aspx]担任主控制器角色。它由应用程序首页的链接调用。 它将处理所有操作共有的通用操作,随后执行与所用链接关联的特定操作。接着,它会将控制权移交给负责执行该操作的某个辅助控制器。从这一刻起,通信将在客户端与该特定控制器之间进行,不再通过主控制器 [main.aspx]。 因此,我们不再处于由单一控制器过滤所有请求的 MVC 框架中。如前所述,上述每个控制器均可通过容器机制在单一页面中呈现多个视图。
不再由单一控制器决定向客户端发送哪些视图,这带来了一些弊端。以错误处理为例。应用程序暴露的每个操作都可能需要显示一个错误视图。 每个控制器 [applix.aspx] 都将拥有其专属的视图 [erreurs],因为该视图本质上只是控制器页面中的一个特定容器。无法创建一个由所有独立应用程序共用的统一视图 [erreurs]。 实际上,此类视图通常包含一个返回错误表单的链接,且该表单必须恢复到已验证的状态,以便用户更正错误。这种恢复通过 [VIEWSTATE] 机制实现,但该机制无法在不同的控制器之间工作。 如果应用程序由不同人员开发,根据用户选择的操作,错误页面的外观可能会有所不同,从而影响整个应用程序的统一性。 稍后我们将看到,ASP.NET 为这一特定的视图共享问题提供了解决方案。该方案可作为我们自行构建的新服务器组件。只需在不同应用程序中使用该组件,即可确保整体应用程序的统一性。 更难处理的是操作顺序的问题。当所有请求都通过单一控制器时,该控制器可以验证所请求的操作是否与前一个操作兼容。这种验证代码集中在一个地方。而在这里,我们需要将其分散到不同的控制器中,这会增加整个应用程序的维护难度。
让我们回到上文的应用程序。其登录页面是一个典型的 HTML 页面:
<html>
<head>
<TITLE>Composants ASP Serveur</TITLE>
<meta name="pragma" content="no-cache">
</head>
<frameset rows="130,*" frameborder="0">
<frame name="banner" src="bandeau.htm" scrolling="no">
<frameset cols="200,*">
<frame name="contents" src="options.htm">
<frame name="main" src="main.htm">
</frameset>
<noframes>
<p id="p1">
Ce jeu de frames HTML affiche plusieurs pages Web. Pour afficher ce jeu de
frames, utilisez un navigateur Web qui prend en charge HTML 4.0 et version
ultérieure.
</p>
</noframes>
</frameset>
</html>
该主页由三个名为banner、contents和main的框架组成:
![]() |
放置在 [banner] 框架中的 [bandeau.htm] 页面如下:

其代码 HTML 如下:
<html>
<head>
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE" />
<title>bandeau</title>
</head>
<body>
<P>
<TABLE>
<TR>
<TD><IMG alt="logo université d'angers" src="univ01.gif"></TD>
<TD>Composants serveurs ASP</TD>
</TR>
</TABLE>
</P>
<HR>
</body>
</html>
页面 [options.htm] 位于横幅 [contents] 中。这是一个链接集合:
![]() | |
各个链接均指向主控件 [main.aspx],并带有参数 [action] 来指定要执行的操作。要求将链接的目标显示在框架 [main] 中(target="main")。
在框架 [main] 中显示的首个页面是 [main.htm]:
|
主控制器 [main.aspx, main.aspx.vb] 如下:
[main.aspx]
[main.aspx.vb]
Public Class main
继承自 System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) 通过 MyBase.Load 句柄
' 获取要执行的操作
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "label"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' 执行操作
根据 action 进行判断
Case "label"
Server.Transfer("form2.aspx")
Case "button"
Server.Transfer("form3.aspx")
Case "textbox1"
Server.Transfer("form4.aspx")
Case "textbox2"
Server.Transfer("form5.aspx")
Case "dropdownlist"
Server.Transfer("form6.aspx")
“listbox”框
Server.Transfer("form7.aspx")
“复选框”
Server.Transfer("form8.aspx")
“复选框列表”
Server.Transfer("form8b.aspx")
案例 "面板"
Server.Transfer("form9.aspx")
Case Else
Server.Transfer("form2.aspx")
End Select
End Sub
End Class
我们的控制器非常简单。根据参数 [action] 的值,它将请求的处理转交给相应的页面。与包含链接的 HTML 页面相比,它没有任何额外价值。 不过,只需添加一个身份验证页面,就能看出它的价值。如果用户需要通过身份验证(用户名、密码)才能访问应用程序,那么控制器 [main.aspx] 就是验证身份验证是否成功的好地方。




