2. ASP.NET的简要介绍
本文将通过几个示例,介绍ASP.NET的相关概念,这些概念将在本文后续内容中派上用场。本介绍无法涵盖Web应用程序中客户端/服务器交互的全部细节。如需深入了解,可参阅:
- ASP.NET 编程 [使用 ASP.NET 1.1 进行 Web 开发 (2004)]
本简介旨在帮助希望快速入门,且暂时愿意忽略某些可能重要细节的读者。文档后续部分将深入探讨这些细节。熟悉 ASP.NET 的读者可直接跳至第 3 节。
2.1. 一个示例项目
2.1.1. 创建项目
![]() |
- 在 [1] 中,使用 Visual Web Developer 创建一个新项目
- 在 [2] 中,选择 Visual C# 网页项目
- 在 [3] 中,指定要创建一个 Web 应用程序 ASP.NET
- 在 [4] 中,为应用程序命名。系统将以此名称为该项目创建一个文件夹。
- 在 [5] 中,指定项目文件夹 [4] 的父文件夹
![]() |
- 在 [6] 中,创建的项目
- [Default.aspx] 是一个默认生成的网页。它包含 HTML 标签和 ASP.NET 标签
- [Default.aspx.cs] 包含用于管理用户在浏览器中显示的 [Defaul.aspx] 页面上触发的事件的代码
- [Default.aspx.designer.cs] 包含页面 [Default.aspx] 中 ASP.NET 组件的列表。 放置在 [Default.aspx] 页面上的每个 ASP.NET 组件都会在 [Default.aspx.designer.cs] 中生成该组件的声明。
- [Web.config] 是项目 ASP.NET 的配置文件。
- [References] 列出了 Web 项目所使用的 DLL 文件。这些 DLL 文件是项目将要使用的类库。 [7] 列出了默认添加到项目引用中的 DLL 文件。 其中大部分都是多余的。如果项目需要使用 [7] 中未列出的某个 DLL,可以通过 [8] 将其添加。
2.1.2. [Default.aspx]页面
若通过 [Ctrl-F5] 运行该项目,浏览器中将显示页面 [Default.aspx]:
![]() |
- 在 [1] 中,显示的是 Web 项目的 URL 页面。Visual Web Developer 内置了一个 Web 服务器,当请求运行项目时,该服务器就会启动。它监听一个随机端口,此处为 1490。 监听端口通常为 80 端口。在 [1] 中,未请求任何页面。此时将显示 [Default.aspx] 页面,因此该页面被命名为默认页面。
- 在 [2] 中,页面 [Default.aspx] 为空。
- 在 Visual Web Developer 中,页面 [Default.aspx] [3] 可以通过可视化方式([Design] 选项卡)或使用标签([Source] 选项卡)进行构建
- 在 [4] 中,页面 [Defaul.aspx] 处于 [Design] 模式。通过将 [5] 工具箱中的组件拖放到该页面上即可构建它。
![]() |
[Source] [6] 模式可访问该页面的源代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
</div>
</form>
</body>
</html>
- 第 1 行是一条 ASP.NET 指令,列出了页面的某些属性
- 指令 Page 适用于网页。还有其他指令,如 Application、WebService 等,适用于其他对象 ASP.NET
- CodeBehind 属性指明了管理页面事件的文件
- 属性 Language 指定文件 CodeBehind 所使用的 .NET 语言
- 属性 Inherits 表示在文件 CodeBehind 中定义的类名
- 属性 AutoEventWireUp="true" 表示 [Default.aspx] 中的事件与其在 [Defaul.aspx.cs] 中的处理程序之间的关联是通过事件名称建立的。因此,[Default.aspx] 页面上的 Load 事件将由 Inherits 属性定义的 Intro._Default 类中的 Page_Load 方法处理。
- 第 4-14 行使用以下经典标签描述页面 [Defaul.aspx]:
- 经典的 HTML 标签,例如 <body> 或 <div>
- ASP.NET。这些标签具有 runat="server" 属性。ASP.NET 标签在页面发送给客户端之前由 Web 服务器进行处理。 它们会被转换为 HTML 标签。因此,客户端浏览器接收到的是一份标准的 HTML 页面,其中已不再包含 ASP.NET 标签。
[Default.aspx]页面可直接通过其源代码进行修改。这有时比通过[Design]模式操作更为简便。我们按以下方式修改源代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Introduction ASP.NET</title>
</head>
<body>
<h3>Introduction à ASP.NET</h3>
<form id="form1" runat="server">
<div>
</div>
</form>
</body>
</html>
在第 6 行,我们使用标签 HTML <title> 为页面设置标题。在第 9 行,我们在页面正文中插入文本 (<body>)。如果运行项目(Ctrl-F5),浏览器中将显示以下结果:
![]() |
2.1.3. 文件 [Default.aspx.designer.cs] 和 [Default.aspx.cs]
文件 [Default.aspx.designer.cs] 声明了页面 [Defaul.aspx] 的组件:
//------------------------------------------------------------------------------
// <自动生成>
// 此代码由工具生成。
// 运行时版本:2.0.50727.3603
//
// 对该文件所做的修改可能会导致行为异常,并且如果
// 代码重新生成时,这些修改将被丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace Intro {
public partial class _Default {
/// <summary>
/// form1 控件。
/// </summary>
/// <remarks>
/// 自动生成的字段。
/// 如需修改,请将字段声明从设计文件移至代码 behind 文件中。
/// </remarks>
protected global::System.Web.UI.HtmlControls.HtmlForm form1;
}
}
该文件中列出了页面 [Default.aspx] 中具有标识符的 ASP.NET 组件。 它们对应于 [Default.aspx] 中具有 runat="server" 属性和 id 属性的标签。因此,上述第 23 行中的组件对应于标签
<form id="form1" runat="server">
。
开发人员很少直接操作文件 [Default.aspx.designer.cs]。不过,该文件对于识别特定组件的类非常有用。如下所示,组件 form1 的类型为 HtmlForm。 开发人员可以进一步探索该类,以了解其属性和方法。页面 [Default.aspx] 中的组件由文件 [Default.aspx.cs] 中的类所使用:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Intro
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
需要注意的是,文件 [Default.aspx.cs] 和 [Default.aspx.designer.cs] 中定义的类是相同的(第 10 行):Intro._Default。 正是关键字 partial 使得一个类的声明可以扩展到多个文件中,此处为两个文件。
在上文第 10 行中,我们可以看到类 [_Default] 扩展了类 [Page] 并继承了其事件。 其中之一是 Load 事件,该事件在网页由 Web 服务器加载时触发。 第 12 行,方法 Page_Load 负责处理该页面的 Load 事件。通常在此处初始化页面,然后才在客户端浏览器中显示。此处,方法 Page_Load 不执行任何操作。
与网页关联的类(此处为 Intro._Default 类)在客户端请求开始时创建,并在向客户端发送响应后销毁。因此,它无法用于在两次请求之间存储信息。为此,必须使用用户会话的概念。
2.2. 网页 ASP.NET 的事件
我们构建以下页面 [Default.aspx]:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Introduction ASP.NET</title>
</head>
<body>
<h3>Introduction à ASP.NET</h3>
<form id="form1" runat="server">
<div>
<table>
<tr>
<td>
Nom</td>
<td>
<asp:TextBox ID="TextBoxNom" runat="server"></asp:TextBox>
</td>
<td>
</td>
</tr>
<tr>
<td>
Age</td>
<td>
<asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
</td>
<td>
</td>
</tr>
</table>
</div>
<asp:Button ID="ButtonValider" runat="server" Text="Valider" />
<hr />
<p>
Evénements traités par le serveur</p>
<p>
<asp:ListBox ID="ListBoxEvts" runat="server"></asp:ListBox>
</p>
</form>
</body>
</html>
该页面的 [Design] 模式如下:
![]() |
文件 [Default.aspx.designer.cs] 如下:
namespace Intro {
public partial class _Default {
protected global::System.Web.UI.HtmlControls.HtmlForm form1;
protected global::System.Web.UI.WebControls.TextBox TextBoxNom;
protected global::System.Web.UI.WebControls.TextBox TextBoxAge;
protected global::System.Web.UI.WebControls.Button ButtonValider;
protected global::System.Web.UI.WebControls.ListBox ListBoxEvts;
}
}
其中包含页面 [Default.aspx] 中所有具有标识符的组件 ASP.NET。
我们将文件 [Default.aspx.cs] 更新如下:
using System;
namespace Intro
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Init(object sender, EventArgs e)
{
// 记录事件
ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
}
protected void Page_Load(object sender, EventArgs e)
{
// 记录事件
ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
}
protected void ButtonValider_Click(object sender, EventArgs e)
{
// 记录事件
ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
}
}
}
类 [_Default](第 5 行)处理三个事件:
- Init 事件(第 7 行),在页面初始化时触发
- Load 事件(第 13 行),在网页由 Web 服务器加载后触发。Init 事件发生在 Load 事件之前。
- ButtonValider按钮的Click事件(第19行),该事件在用户点击[Valider]按钮时触发
对这三个事件的处理方式是:向名为 ListBoxEvts 的 Listbox 组件添加一条消息。该消息会显示事件发生的时间和事件名称。每条消息都位于列表的开头,因此列表顶部的消息是最新的。
运行该项目后,将显示以下页面:
![]() |
从 [1] 可以看出,事件 Page_Init 和 Page_Load 是按此顺序发生的。 需要提醒的是,列表中最新的事件位于最上方。当浏览器通过其 URL [2] 直接请求页面 [Default.aspx] 时,它是通过一个名为 HTTP (HyperText 传输协议)来调用 GET。页面加载到浏览器后,用户将触发页面上的事件。 例如,用户会点击 [Valider] [3] 按钮。 页面在浏览器中加载完成后,用户触发的事件会向页面 [Default.aspx] 发起请求,但这次使用的是名为 HTTP 的命令,该命令又调用了 POST。简而言之:
- 页面 P 在浏览器中的初始加载由操作 HTTP GET 完成
- 随后页面上发生的事件每次都会向同一页面 P 发起新的请求,但这次使用的命令是 HTTP POST。 页面 P 可以判断自身是通过 GET 还是 POST 命令被请求的,这使其能够在必要时采取不同的行为——而大多数情况下确实需要如此。
页面的初始请求 ASPX:GET
![]() |
- 转换为 [1] 后,浏览器通过无参数的 HTTP GET 命令请求页面 ASPX。
- 在 [2] 中,Web 服务器向其发送响应流 HTML,即所请求页面 ASPX 的翻译。
处理浏览器显示页面上发生的事件:POST
![]() |
- 转换为 [1],当 HTML 页面发生事件时, 浏览器请求页面 ASPX(该页面已通过操作 GET 获取),此次请求使用命令 HTTP POST 并附带参数。 这些参数是浏览器显示的页面 HTML 中 <form> 标签内组件的值。这些值被称为客户端提交的值。页面 ASPX 将利用这些值来处理客户端的请求。
- 在 [2] 中, Web 服务器将响应数据流 HTML 发送给客户端,该数据流是 POST 最初请求的页面 ASPX 的翻译版本,或者在发生页面转移或重定向时,是其他页面的翻译版本。
让我们回到我们的示例页面:
![]() |
- 在 [2] 中,该页面是由 GET 生成的。
- 在 [1] 中,可以看到在 GET 过程中发生的两个事件
如果用户在上面点击 [Valider] [3] 按钮,则将通过 POST 请求 [Default.aspx] 页面。 该 POST 请求将携带参数,这些参数即为页面 [Default.aspx] 中 <form> 标签内所有组件的值: 两个 TextBox、[TextBoxNom, TextBoxAge],按钮 [ButtonValider],列表 [ListBoxEvts]。这些组件的提交值如下:
- TextBox:输入的值
- Button:按钮文本,此处为“确认”
- Listbox:在 ListBox 中选定的消息文本
响应 POST 后,将跳转至页面 [4]。随后又返回页面 [Default.aspx]。这是正常行为,除非页面事件处理程序执行了页面转移或重定向。 可以看到发生了两个新事件:
- 在页面加载时触发的事件 Page_Load
- 事件 ButtonValider_Click,由点击按钮 [Valider] 触发
可以注意到:
- 事件 Page_Init 并未在操作 HTTP POST 时发生,而但在事件 HTTP GET 上却发生了
- 事件 Page_Load 无论是在 GET 还是 POST 上都会发生。 通常需要通过此方法来判断当前处理的是 GET 还是 POST。
- 在处理完 POST 之后,包含事件处理程序所做修改的 [Default.aspx] 页面被发回给客户端。情况总是如此。一旦页面 P 的事件处理完毕,该页面 P 就会被发回给客户端。 有两种方式可以打破这一规则。最后执行的事件处理程序可以
- 将执行流转移至另一页面 P2。
- 将客户端浏览器重定向至另一页面 P2。
在这两种情况下,返回给浏览器的都是页面 P2。这两种方法存在差异,我们稍后将详细讨论。
- 事件 ButtonValider_Click 发生在事件 Page_Load 之后。因此,正是该处理程序可以决定是进行转移还是重定向到页面 P2。
- 事件列表 [4] 保留了页面 [Default.aspx] 初始加载时显示的两个事件 GET。 考虑到页面 [Default.aspx] 是在 POST 期间重新创建的,这一现象令人惊讶。理应能找到带有其设计值的页面 [Default.aspx],因此 ListBox 应为空。 随后,Page_Load和ButtonValider_Click处理程序的执行本应在此页面中写入两条消息。然而实际却发现了四条。这正是由VIEWSTATE的机制所导致的。 在初始的 GET 过程中,Web 服务器发送了包含 HTML 标签 <input type="hidden" ...>(即隐藏字段,见下文第 10 行)的 [Default.aspx] 页面。
在ID字段“__VIEWSTATE”中,Web服务器会将页面中所有组件的值进行编码处理。无论是初始的GET,还是随后的POST,服务器都会进行此操作。 当页面 P 上出现 POST 时:
- 浏览器在请求页面 P 时,会在请求中发送位于 <form> 标签内的所有组件的值。 上文可见,组件“__VIEWSTATE”位于<form>标签内。因此,其值会在发生POST时被发送至服务器。
- 页面 P 被实例化,并使用其构造值进行初始化
- 组件“__VIEWSTATE”用于将组件恢复为页面 P 先前发送时的原始值。 例如,事件列表[4]便能恢复其最初作为对浏览器初始请求GET的响应被发送时所包含的前两条消息。
- 随后,页面 P 的组件将采用浏览器提交的值。此时,页面 P 的表单处于用户提交时的状态。
- 处理事件 Page_Load。在此,它向事件列表 [4] 中添加了一条消息。
- 处理了触发 POST 的事件。在此,ButtonValider_Click 向事件列表 [4] 中添加了一条消息。
- 页面 P 被返回。组件的值如下:
- 要么是提交的值,即 c.a.d。要么是组件在表单提交至服务器时在表单中的值
- 或由某个事件处理程序提供的值。
在本例中,
- 两个组件 TextBox 将恢复其提交时的值,因为事件处理程序未对其进行修改
- 事件列表 [4] 恢复其提交时的值,即 c.a.d。该列表包含所有已注册的事件,以及由方法 Page_Load 和 ButtonValider_Click 创建的两个新事件。
VIEWSTATE机制可在每个组件级别启用或禁用。让我们为[ListBoxEvts]组件禁用该机制:
![]() |
- 在 [1] 中,组件 [ListBoxEvts] 的 VIEWSTATE 已被禁用。TextBox 和 [2] 的相应组件默认处于启用状态。
- 在 [3] 中,初始 GET 之后返回的两个事件
![]() |
- 在 [4] 中,表单已填写完毕并点击了 [Valider] 按钮。将执行 POST 跳转至 [Default.aspx] 页面。
- 在 [6] 中,点击按钮 [Valider] 后返回的结果
- VIEWSTATE机制已启用,这解释了为何TextBox和[7]在[4]中保留了其已提交的值
- VIEWSTATE机制被禁用,这解释了为何[ListBoxEvts]和[8]组件未能保留其[5]内容。
2.3. 提交值的管理
这里我们将关注当用户点击按钮 [Valider] 时,由两个 TextBox 提交的值。[Default.aspx] 页面在 [Design] 模式下的变化如下:
![]() |
在 [1] 中添加的元素源代码如下:
<p>
Eléments postés au serveur :
<asp:Label ID="LabelPost" runat="server"></asp:Label>
</p>
我们将使用组件 [LabelPost] 来显示在两个 TextBox 和 [2] 中输入的值。事件处理程序 [Default.aspx.cs] 的代码如下所示:
using System;
namespace Intro
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Init(object sender, EventArgs e)
{
// 记录事件
ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
}
protected void Page_Load(object sender, EventArgs e)
{
// 记录该事件
ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
}
protected void ButtonValider_Click(object sender, EventArgs e)
{
// 记录该事件
ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
// 显示姓名和年龄
LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
}
}
}
第 24 行,更新组件 LabelPost:
- LabelPost 属于 [System.Web.UI.WebControls.Label] 类型(参见 Default.aspx.designer.cs)。其属性 Text 表示组件显示的文本。
- TextBoxNom 和 TextBoxAge 属于 [System.Web.UI.WebControls.TextBox] 类型。 TextBox 组件的 Text 属性即为输入框中显示的文本。
- 方法 Trim() 会删除字符串前后的空格
如前所述,当执行方法 ButtonValider_Click 时,页面组件的值与用户提交页面时的值相同。 因此,两个 TextBox 方法的 Text 属性值即为用户在浏览器中输入的文本。
以下是一个示例:
![]() |
- 在 [1] 中,提交的值
- 在 [2] 中,服务器响应。
- 在 [3] 中,TextBox 通过已激活的 VIEWSTATE 机制恢复了其提交时的值
- 在 [4] 中,ListBoxEvts 组件的消息源自 Page_Init、Page_Load、 ButtonValider_Click 以及一个被禁用的 VIEWSTATE
- 在 [5] 中,组件 LabelPost 通过方法 ButtonValider_Click 获取了其值。 我们确实成功获取了用户在两个 TextBox 和 [1] 组件中输入的两个值。
上文可见,年龄字段提交的值为字符串“yy”,这是一个非法值。我们将向页面添加名为“验证器”的组件。它们用于验证提交数据的有效性。该有效性可在两个位置进行验证:
- 客户端。验证器的配置选项允许选择是否在浏览器端进行测试。若启用,则由嵌入在页面中的 HTML 代码执行验证。 当用户提交表单中的输入值时,这些值首先由 JavaScript 代码进行验证。如果任何一项验证失败,则不会执行后续操作。这样可以避免与服务器的往返通信,从而提高页面的响应速度。
- 在服务器端。虽然客户端的验证可能是可选的,但在服务器端,无论客户端是否进行了验证,这些验证都是强制性的。事实上,当页面接收提交的值时,它无法确定这些值在发送前是否已被客户端验证过。因此,在服务器端,开发人员必须始终验证所提交数据的有效性。
页面 [Default.aspx] 的演变如下:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Introduction ASP.NET</title>
</head>
<body>
<h3>Introduction à ASP.NET</h3>
<form id="form1" runat="server">
<div>
<table>
<tr>
<td>
Nom</td>
<td>
<asp:TextBox ID="TextBoxNom" runat="server"></asp:TextBox>
</td>
<td>
<asp:RequiredFieldValidator ID="RequiredFieldValidatorNom" runat="server"
ControlToValidate="TextBoxNom" Display="Dynamic"
ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td>
Age</td>
<td>
<asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
</td>
<td>
<asp:RequiredFieldValidator ID="RequiredFieldValidatorAge" runat="server"
ControlToValidate="TextBoxAge" Display="Dynamic"
ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
<asp:RangeValidator ID="RangeValidatorAge" runat="server"
ControlToValidate="TextBoxAge" Display="Dynamic"
ErrorMessage="Tapez un nombre entre 1 et 150 !" MaximumValue="150"
MinimumValue="1" Type="Integer"></asp:RangeValidator>
</td>
</tr>
</table>
</div>
<asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click"
Text="Valider" CausesValidation="False"/>
<hr />
<p>
Evénements traités par le serveur</p>
<p>
<asp:ListBox ID="ListBoxEvts" runat="server" EnableViewState="False">
</asp:ListBox>
</p>
<p>
Eléments postés au serveur :
<asp:Label ID="LabelPost" runat="server"></asp:Label>
</p>
<p>
Eléments validés par le serveur :
<asp:Label ID="LabelValidation" runat="server"></asp:Label>
</p>
<asp:Label ID="LabelErreursSaisie" runat="server" ForeColor="Red"></asp:Label>
</form>
</body>
</html>
已在第 20、32 和 35 行添加了验证器。第 58 行,使用 Label 组件来显示有效的提交值。 第 60 行,使用组件 Label 在存在输入错误时显示错误消息。
[Design] 模式下的 [Default.aspx] 页面如下:
![]() |
- 组件 [1] 和 [2] 属于 RequiredFieldValidator 类型。该验证器用于检查输入字段是否为空。
- 组件 [3] 属于类型 RangeValidator。该验证器用于检查输入字段的值是否在两个边界值之间。
- 在 [4] 中,包含验证器 [1] 的属性。
我们将通过 [Default.aspx] 页面代码中的标签来介绍这两种验证器:
<asp:RequiredFieldValidator ID="RequiredFieldValidatorNom" runat="server"
ControlToValidate="TextBoxNom" Display="Dynamic"
ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
- ID:组件标识符
- ControlToValidate:被验证值的组件名称。此处要求组件 TextBoxNom 的值不能为空(空字符串或连续空格)
- ErrorMessage:数据无效时在验证器中显示的错误信息。
- EnableClientScript:布尔值,用于指示验证器是否也应在客户端执行。若未如上所述显式设置,该属性的默认值为 True。
- Display:验证器的显示模式。共有两种模式:
- static(默认):即使未显示错误消息,验证器也会在页面上占据空间
- dynamic:如果验证器未显示错误消息,则不占用页面空间。
<asp:RangeValidator ID="RangeValidatorAge" runat="server"
ControlToValidate="TextBoxAge" Display="Dynamic"
ErrorMessage="Tapez un nombre entre 1 et 150 !" MaximumValue="150"
MinimumValue="1" Type="Integer"></asp:RangeValidator>
- 类型:被验证数据的类型。此处年龄为整数。
- MinimumValue, MaximumValue:被验证值必须位于的范围
触发 POST 的组件配置对验证模式起决定性作用。此处该组件为按钮 [Valider]:
<asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click" Text="Valider" CausesValidation="True" />
- CausesValidation:用于设置服务器端验证的自动模式或名称。若未明确指定,该属性的默认值为“True”。在此情况下,
- 客户端将执行 EnableClientScript 至 True 之间的验证器。只有当所有客户端验证器均成功时,才会执行 POST。
- 在服务器端,在处理触发 POST 的事件之前,页面上的所有验证器都会自动执行。在此情况下,它们将在执行 ButtonValider_Click 方法之前运行。 在此方法中,可以判断所有验证是否均已通过。若全部通过,Page.IsValid 为“True”;否则为“False”。在后一种情况下,可以停止处理触发 POST 的事件。 提交的页面将按原样返回。验证失败的字段将显示其错误信息(ErrorMessage属性)。
如果 CausesValidation 的值为 False,则
- 客户端将不执行任何验证器
- 在服务器端,开发人员需要自行调用页面验证器的执行。这通过方法 Page.Validate() 来实现。根据验证结果,该方法将属性 Page.IsValid 设置为“True”或“False”。
在 [Default.aspx.cs] 中,ButtonValider_Click 的处理代码如下所示:
protected void ButtonValider_Click(object sender, EventArgs e)
{
// 记录事件
ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
// 显示姓名和年龄
LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
// 页面是否有效?
Page.Validate();
if (!Page.IsValid)
{
// 全局错误信息
LabelErreursSaisie.Text = "Veuillez corriger les erreurs de saisie...";
LabelErreursSaisie.Visible = true;
return;
}
// 隐藏错误信息
LabelErreursSaisie.Visible = false;
// 显示已验证的姓名和年龄
LabelValidation.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
}
若按钮 [Valider] 的属性 CausesValidation 设置为 True,且验证器的属性 EnableClientScript 设置为 True, 则只有当提交的值有效时,才会执行 ButtonValider_Click 方法。 那么,第8行开始的代码有何意义?需注意,客户端程序始终可能向页面[Default.aspx]提交未经验证的值。因此,该页面必须始终重新执行有效性验证。
- 第 8 行:启动页面中所有验证器的执行。如果按钮 [Valider] 的属性 CausesValidation 设置为 True,则此操作会自动执行,无需重复。 此处存在冗余。
- 第 9-15 行:当其中一个验证器失败时
- 第 16-19 行:所有验证器均通过的情况
以下是两个执行示例:
![]() |
- 在 [1] 中,这是一个执行示例,其中:
- 按钮 [Valider] 的属性从 CausesValidation 变为 True
- 验证器的属性从 EnableClientScript 变为 True
页面中的 JavaScript 代码在客户端执行了验证器,并显示了 [2] 错误消息。如已提交元素的标签 [3] 所示,未向服务器发送 POST。
- 在 [4] 中,以下是一个执行示例:
- 按钮 [Valider] 的属性 CausesValidation 设置为 False
- 验证器的 EnableClientScript 属性变为 False
服务器端执行的验证器显示了 [5] 错误消息。如 [6] 所示,确实向服务器发送了 POST。 在 [7] 中,显示了方法 [ButtonValider_Click] 在发生输入错误时返回的错误消息。
![]() |
- 在 [8] 中,这是一个使用有效数据生成的示例。[9,10] 显示已提交的元素已通过验证。 进行重复测试时,需将标签 [LabelValidation] 的属性 EnableViewState 设置为 False,以避免验证消息在多次执行过程中持续显示。
2.4. 应用程序作用域数据管理
让我们回顾一下 ASPX 页面的执行架构:
![]() |
ASPX页面的类在客户端请求开始时被实例化,并在请求结束时被销毁。因此,它无法用于在两次请求之间存储数据。我们可能希望存储两种类型的数据:
- 由 Web 应用程序所有用户共享的数据。这些通常是只读数据。有三个文件用于实现这种数据共享:
- [Web.Config]:应用程序配置文件
- [Global.asax, Global.asax.cs]:用于定义一个称为“应用程序全局类”的类,其生命周期与应用程序一致,并包含该应用程序某些事件的处理程序。
全局应用程序类用于定义可供所有用户的所有请求访问的数据。
- 同一客户端的请求共享的数据。这些数据存储在一个名为 Session 的对象中。此时,我们称之为客户端会话来指代客户端的内存。一个客户端的所有请求都可以访问该会话。它们可以在其中存储和读取信息
![]() |
上文展示了 ASPX 页面可访问的内存类型:
- 应用程序内存,其中大多包含只读数据,且所有用户均可访问。
- 特定用户的内存(即会话),其中包含可读写数据,同一用户的后续请求均可访问该内存。
- 上图未显示的是请求内存(或称请求上下文)。一个用户的请求可能由多个连续的 ASPX 页面处理。请求上下文使页面 1 能够向页面 2 传递信息。
本文关注的是作用域为 Application 的数据,即所有用户共享的数据。应用程序的全局类可按以下方式创建:
![]() |
- 在 [1] 中,向项目添加一个新元素
- 在 [2] 中,添加全局应用程序类
- 在 [3] 中,为新元素保留默认名称 [Global.asax]
![]() |
- 在 [4] 中,向项目添加了两个新文件
- 在 [5] 中,显示 [Global.asax] 的标记
<%@ Application Codebehind="Global.asax.cs" Inherits="Intro.Global" Language="C#" %>
- 标签 Application 取代了之前用于 [Default.aspx] 的标签 Page。它标识全局应用程序类
- 代码 behind:指定定义全局应用程序类的文件
- Inherits:定义该类的名称
生成的 Intro.Global 类如下:
using System;
namespace Intro
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
}
protected void Session_Start(object sender, EventArgs e)
{
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
}
protected void Application_Error(object sender, EventArgs e)
{
}
protected void Session_End(object sender, EventArgs e)
{
}
protected void Application_End(object sender, EventArgs e)
{
}
}
}
- 第 5 行:全局应用程序类继承自类 HttpApplication
该类生成时包含应用程序事件处理程序的骨架:
- 第 8、38 行:处理事件 Application_Start(应用程序启动)和 Application_End(Web 服务器停止或管理员卸载应用程序时应用程序结束)
- 第 13、33 行: 处理事件 Session_Start(新客户到达或现有会话过期时启动新客户会话)和 Session_End(客户会话结束,无论是通过编程显式结束,还是因超过会话允许时长而隐式结束)。
- 第 28 行:处理事件 Application_Error(出现应用程序代码未处理的异常并上报至服务器)
- 第 18 行:处理事件 Application_BeginRequest(收到新请求)。
- 第 23 行:处理事件 Application_AuhenticateRequest(当用户通过身份验证时发生)。
方法 [Application_Start] 通常用于根据 [Web.Config] 中包含的信息初始化应用程序。在项目初始创建时生成的该方法如下所示:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
...
</configSections>
<appSettings/>
<connectionStrings/>
<system.web>
...
</system.web>
<system.codedom>
....
</system.codedom>
<!--
La section system.webServer est requise pour exécuter ASP.NET AJAX sur Internet
Information Services 7.0. Elle n'est pas nécessaire pour les versions précédentes d'IIS.
-->
<system.webServer>
...
</system.webServer>
<runtime>
....
</runtime>
</configuration>
对于我们当前的应用程序,该文件是多余的。即使将其删除或重命名,应用程序仍能正常运行。我们将重点关注第 8 行和第 9 行的标签:
- <appsettings> 用于定义信息字典
- <connectionStrings> 用于定义数据库连接字符串
让我们来看一下以下 [Web.config] 文件:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
...
</configSections>
<appSettings>
<add key="cle1" value="valeur1"/>
<add key="cle2" value="valeur2"/>
</appSettings>
<connectionStrings>
<add connectionString="connectionString1" name="conn1"/>
</connectionStrings>
<system.web>
...
该文件可由以下全局应用程序类调用:
using System;
using System.Configuration;
namespace Intro
{
public class Global : System.Web.HttpApplication
{
public static string Param1 { get; set; }
public static string Param2 { get; set; }
public static string ConnString1 { get; set; }
public static string Erreur { get; set; }
protected void Application_Start(object sender, EventArgs e)
{
try
{
Param1 = ConfigurationManager.AppSettings["cle1"];
Param2 = ConfigurationManager.AppSettings["cle2"];
ConnString1 = ConfigurationManager.ConnectionStrings["conn1"].ConnectionString;
}
catch (Exception ex)
{
Erreur = string.Format("Erreur de configuration : {0}", ex.Message);
}
}
protected void Session_Start(object sender, EventArgs e)
{
}
}
}
- 第 8-11 行:四个静态属性 P。由于类 Global 的生命周期与应用程序相同,因此对应用程序的任何请求均可通过语法 Global.P 访问这些属性 P。
- 第 17-19 行:可通过类 [System.Configuration.ConfigurationManager] 访问文件 [Web.config]
- 第 17-18 行:通过 key 属性获取 [Web.config] 文件中 <appSettings> 标签的元素。
- 第 19 行:通过 name 属性,从 [Web.config] 文件中获取 <connectionStrings> 标签的元素。
第 8-11 行的静态属性可在加载的 ASPX 页面的任何事件处理程序中访问。我们在 [Default.aspx] 页面的 [Page_Load] 事件处理程序中使用它们:
protected void Page_Load(object sender, EventArgs e)
{
// 记录该事件
ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
// 从全局应用程序类中获取信息
LabelGlobal.Text = string.Format("Param1={0},Param2={1},ConnString1={2},Erreur={3}", Global.Param1, Global.Param2, Global.ConnString1, Global.Erreur);
}
- 第 6 行:应用程序全局类的四个静态属性被用于为页面 [Default.aspx] 中的一个新标签提供数据
![]() |
运行后,我们得到以下结果:
![]() |
上文可见,[web.config] 的参数已被正确检索。全局应用程序类是存储所有用户共享信息的理想位置。
2.5. 会话作用域数据管理
这里我们关注如何在特定用户的请求过程中保存信息:
![]() |
每个用户都有自己的内存,称为会话。
我们已看到全局应用程序类拥有两个事件处理程序:
- Session_Start:会话开始
- Session_end:会话结束
会话机制的实现方式如下:
- 当用户发出首次请求时,Web 服务器会生成一个会话令牌并分配给该用户。该令牌是针对每位用户唯一的字符串。服务器会在对用户首次请求的响应中发送该令牌。
- 在后续请求中,用户(即网页浏览器)会在请求中包含分配给他的会话令牌。因此,Web 服务器能够识别该用户。
- 会话具有有效期。当Web服务器收到用户的请求时,会计算自上次请求以来经过的时间。如果该时间超过了会话的有效期,则会为用户创建一个新的会话。 前一个会话的数据将丢失。在微软的 IIS 网络服务器(Internet Information Server)中,会话默认有效期为 20 分钟。该值可由网络服务器管理员进行修改。
- Web 服务器知道这是用户的首次请求,因为该请求不包含会话令牌。这是唯一的请求。
任何 ASP.NET 页面均可通过页面中的 Session 属性(类型为 [System.Web.SessionState.HttpSessionState])访问用户的会话。我们将使用 HttpSessionState 类的以下 P 属性与 M 方法:
名称 | 类型 | 角色 |
Item[String clé] | P | 会话可以构建为一个字典。Item[clé] 是通过 clé 标识的会话项。 与其写 [HttpSessionState].Item[clé],也可以写成 [HttpSessionState].[clé]。 |
清除 | M | 清空会话词典 |
放弃 | M | 结束会话。此时会话不再有效。用户下一次请求时将启动新的会话。 |
作为用户内存的示例,我们将统计用户点击按钮 [Valider] 的次数。要获得此结果,需要在用户会话中维护一个计数器。
页面 [Default.aspx] 的演变如下:
![]() |
全局应用程序类 [Global.asax.cs] 的演变如下:
using System;
using System.Configuration;
namespace Intro
{
public class Global : System.Web.HttpApplication
{
public static string Param1 { get; set; }
...
protected void Application_Start(object sender, EventArgs e)
{
...
}
protected void Session_Start(object sender, EventArgs e)
{
// 请求计数器
Session["nbRequêtes"] = 0;
}
}
}
第 19 行,利用用户会话存储一个请求计数器,该计数器由键“nbRequêtes”标识。该计数器由页面 [Default.aspx] 中的管理器 [ButtonValider_Click] 进行更新:
using System;
namespace Intro
{
public partial class _Default : System.Web.UI.Page
{
....
protected void ButtonValider_Click(object sender, EventArgs e)
{
// 记录事件
ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
// 显示已发布的姓名和年龄
LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
// 请求次数
Session["nbRequêtes"] = (int)Session["nbRequêtes"] + 1;
LabelNbRequetes.Text = Session["nbRequêtes"].ToString();
// 页面是否有效?
Page.Validate();
if (!Page.IsValid)
{
...
}
...
}
}
}
- 第 16 行:递增请求计数器
- 第 17 行:在页面上显示计数器
以下是一个执行示例:
![]() |
2.6. 在页面加载过程中管理 GET / POST
我们提到针对 ASPX 页面有两种类型的请求:
- 浏览器通过 HTTP 或 GET 命令发出的初始请求。服务器通过发送所请求的页面进行响应。 我们假设该页面是一个表单,c.a.d。在发送的 ASPX 页面中,包含一个 <form runat="server"...> 标签。
- 随后,浏览器针对用户在表单上的某些操作发起后续请求。此时,浏览器会发出请求 HTTP POST。
无论是 GET 请求还是 POST 请求,都会执行 [Page_Load] 方法。 在 GET 期间,该方法通常用于初始化发送给客户端浏览器的页面。随后,通过 VIEWSTATE 的机制,页面保持初始化状态,仅由触发 POST 的事件处理程序进行修改。 在 Page_Load 中无需重置页面。因此,该方法需要判断客户端的请求是 GET 还是 POST。
让我们来看以下示例。我们在页面 [Default.aspx] 中添加了一个下拉列表。该列表的内容将在请求 GET 的处理程序 Page_Load 中定义:
![]() |
下拉列表在 [Default.aspx.designer.cs] 中声明如下:
protected global::System.Web.UI.WebControls.DropDownList DropDownListNoms;
我们将使用 [DropDownList] 类的以下 M 方法和 P 属性:
名称 | 类型 | 角色 |
Items | P | 下拉列表中类型为 ListItem 的元素的集合,类型为 ListItemCollection |
SelectedIndex | P | 当表单提交时,下拉列表中选定项的索引(从0开始) |
SelectedItem | P | 表单提交时,下拉列表中选中的类型为 ListItem 的元素 |
SelectedValue | P | 当表单提交时,下拉列表中选中的 ListItem 类型元素的 string 类型值。我们将随后定义“值”这一概念。 |
下拉列表中元素的类 ListItem 用于生成标签 HTML 中的标签 <option> <select>:
在 <option> 标签中
- textei 是下拉列表中显示的文本
- vali 是浏览器提交的值,如果 textei 是下拉列表中选中的文本
每个选项均可通过使用构造函数 ListItem(string 文本, string 值) 构建的 LisItem 对象生成。
在 [Default.aspx.cs] 中,[Page_Load] 处理程序的代码如下所示:
protected void Page_Load(object sender, EventArgs e)
{
// 记录事件
...
// 从全局应用程序类中获取信息
...
// 仅在 GET 初始化时初始化名称下拉列表
if (!IsPostBack)
{
for (int i = 0; i < 3; i++)
{
DropDownListNoms.Items.Add(new ListItem("nom"+i,i.ToString()));
}
}
}
- 第 8 行:类 Page 具有一个布尔类型的属性 IsPostBack。实际上,这表示用户的请求是一个 POST。 因此,第10-13行仅针对客户端初始的GET进行执行。
- 第 12 行:向 [DropDownListNoms] 列表中添加一个类型为 ListItem(字符串文本,字符串值)的元素。 第 (i+1) 个元素的显示文本为 nomi,若选中该元素,其提交的值将为 i。
修改 [ButtonValider_Click] 处理程序,以显示下拉列表提交的值:
protected void ButtonValider_Click(object sender, EventArgs e)
{
// 记录事件
...
// 显示提交的值
LabelPost.Text = string.Format("nom={0}, age={1}, combo={2}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim(), DropDownListNoms.SelectedValue);
// 请求次数
...
}
第 6 行,通过列表的 SelectedValue 属性获取了列表 [DropDownListNoms] 的已发布值。以下是一个执行示例:
![]() |
- 在 [1] 中,下拉列表的内容位于初始的 GET 之后,且紧接在第一个 POST 之前
- 在 [2] 中,即第一个 POST 之后的页面。
- 在 [3] 中,下拉列表的提交值。对应于列表中选中的 ListItem 的 value 属性。
- 在 [4] 中,即下拉列表。它包含与初始 GET 之后相同的元素。这是由 VIEWSTATE 的机制所决定的。
要理解列表 DropDownListNoms 中的 VIEWSTATE 与 Page_Load 管理器中的 if 测试 (! IsPostBack) 之间的交互,建议读者使用以下配置重新进行前面的测试:
情况 | DropDownListNoms.EnableViewState | [Default.aspx] 中的 Page_Load 执行 if(! IsPostBack) 测试 |
各项测试结果如下:
- 这正是上文所述的情况
- 列表在初始的 GET 时被填充,但在随后的 POST 中未被填充。由于 EnableViewState 返回 false,因此在每次 POST 之后列表均为空
- 列表在初始的 GET 之后以及随后的 POST 过程中均被填充。由于 EnableViewState 对应 vrai, 因此在初始的 GET 之后有 3 个名称,在第一个 POST 之后有 6 个名称,在第二个 POST 之后有 9 个名称,……
- 无论是在初始的GET之后,还是在随后的POST中,列表都会被填满。 由于 EnableViewState 与 faux 相同,因此无论请求是初始的 GET 还是随后的 POST,列表在每次请求中都仅填充 3 个名称。 这与情况1的行为一致。因此,有两种方法可以获得相同的结果。
2.7. VIEWSTATE对ASPX页面元素的管理
默认情况下,ASPX 页面中的所有元素,其 EnableViewState 属性均设置为 True。每次将 ASPX 页面发送至客户端浏览器时, 该页面将包含一个隐藏字段 __VIEWSTATE,其值为一个字符串,该字符串编码了所有属性值在 EnableViewState 到 True 之间的组件的值。 为了尽量缩小该字符串的大小,可以尝试减少那些 EnableViewState 至 True 属性值的组件数量。
回顾一下页面 ASPX 中的组件如何在 POST 执行完成后获取其值:
- ASPX页面被实例化。组件使用其设计值进行初始化。
- 使用浏览器提交的 __VIEWSTATE 值,使组件恢复到上次将页面 ASPX 发送至浏览器时的状态。
- 将浏览器提交的值赋给组件
- 执行事件处理程序。它们可能会修改某些组件的值。
由此流程可推断,以下组件:
- 其值被提交
- 其值被事件处理程序修改
其 EnableViewState 属性可能变为 Faux,因为其 VIEWSTATE 值(步骤 2)将在步骤 3 或 4 中被修改。
我们页面组件的列表可在 [Default.aspx.designer.cs] 中找到:
namespace Intro {
public partial class _Default {
protected global::System.Web.UI.HtmlControls.HtmlForm form1;
protected global::System.Web.UI.WebControls.TextBox TextBoxNom;
protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidatorNom;
protected global::System.Web.UI.WebControls.TextBox TextBoxAge;
protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidatorAge;
protected global::System.Web.UI.WebControls.RangeValidator RangeValidatorAge;
protected global::System.Web.UI.WebControls.DropDownList DropDownListNoms;
protected global::System.Web.UI.WebControls.Button ButtonValider;
protected global::System.Web.UI.WebControls.ListBox ListBoxEvts;
protected global::System.Web.UI.WebControls.Label LabelPost;
protected global::System.Web.UI.WebControls.Label LabelValidation;
protected global::System.Web.UI.WebControls.Label LabelErreursSaisie;
protected global::System.Web.UI.WebControls.Label LabelGlobal;
protected global::System.Web.UI.WebControls.Label LabelNbRequetes;
}
}
这些组件的 EnableViewState 属性值可能如下:
Composant | 已提交的值 | EnableViewState | Pourquoi |
TextBoxNom | 在TextBox中输入的值 | False | 组件的值已发布 |
TextBoxAge | 同上 | ||
RequiredFieldValidatorNom | 无 | False | 缺少组件值的概念 |
RequiredFieldValidatorAge | 同上 | ||
RangeValidatorAge | 同上 | ||
LabelPost | 无 | False | 通过事件处理程序获取其值 |
LabelValidation | 同上 | ||
LabelErreursSaisie | 同上 | ||
LabelGlobal | 同上 | ||
LabelNbRequetes | 同上 | ||
DropDownListNoms | 所选元素的“value” | True | 希望在多次请求中保留列表内容,而无需重新生成 |
ListBoxEvts | 所选元素的“value” | False | 列表内容由事件处理程序生成 |
ButtonValider | 按钮的标签 | False | 组件保留其设计值 |
2.8. 从一个页面跳转到另一个页面
迄今为止,操作 GET 和 POST 始终返回同一页面 [Default.aspx]。 我们将考虑一种情况:一个请求由两个连续的页面 ASPX、[Default.aspx] 和 [Page1.aspx] 处理,其中最后一个页面被返回给客户端。 此外,我们将探讨页面 [Default.aspx] 如何通过一种称为“请求内存”的机制向页面 [Page1.aspx] 传递信息。
![]() |
我们构建页面 [Page1.aspx]:
![]() |
- 在 [1] 中,向项目
- 在 [2] 中,添加一个名为 [Page1.aspx] 的元素 [Web Form] [3]
![]() |
- 在 [4] 中,添加的页面
- 在 [5] 中,页面构建完成后
[Page1.aspx]的源代码如下:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page1.aspx.cs" Inherits="Intro.Page1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Page1</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>
Page 1</h1>
<asp:Label ID="Label1" runat="server"></asp:Label>
<br />
<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Retour
vers page [Default]</asp:HyperLink>
</div>
</form>
</body>
</html>
- 第13行:一个标签,用于显示由页面 [Default.aspx] 传输的信息
- 第15行:一个指向页面[Default.aspx]的链接HTML。当用户点击该链接时,浏览器会通过操作GET请求页面[Default.aspx]。 此时,页面 [Default.aspx] 会被加载,就好像用户直接在浏览器中输入了其 URL 一样。
页面 [Default.aspx] 会新增一个类型为 LinkButton 的组件:
![]() |
该新组件的源代码如下:
<asp:LinkButton ID="LinkButtonToPage1" runat="server" CausesValidation="False"
EnableViewState="False" onclick="LinkButtonToPage1_Click">Forward vers Page1</asp:LinkButton>
- CausesValidation="False":点击该链接将触发从 POST 跳转至 [Defaul.aspx]。 组件 [LinkButton] 的行为与组件 [Button] 相同。在此,我们不希望点击链接触发验证器的执行。
- EnableViewState="False":无需在请求之间保留链接的状态。它将保持其设计时的值。
- onclick="LinkButtonToPage1_Click":该方法名称,在 [Defaul.aspx.cs] 中负责处理组件 LinkButtonToPage1 上的事件 Click。
LinkButtonToPage1_Click 处理程序的代码如下:
// 跳转至 Page1
protected void LinkButtonToPage1_Click(object sender, EventArgs e)
{
// 将信息写入上下文
Context.Items["msg1"] = "Message de Default.aspx pour Page1";
// 将请求传递给 Page1
Server.Transfer("Page1.aspx",true);
}
第 7 行,请求通过方法 [Server.Transfer] 传递给页面 [Page1.aspx]。 该方法的第二个参数(即 true)表示,需将 POST 执行期间发送给 [Default.aspx] 的所有信息传递给 [Page1.aspx]。 例如,这使得 [Page1.aspx] 能够通过名为 Request.Form 的集合访问已提交的值。 第 5 行使用了所谓的请求上下文。可以通过 Page 类的 Context 属性访问该上下文。 该上下文可作为处理同一请求的不同页面(此处为 [Default.aspx] 和 [Page1.aspx])之间的共享内存。为此,我们使用了 Items 字典。
当 [Page1.aspx] 通过操作 Server.Transfer("Page1.aspx",true) 加载时, 其效果就如同 [Page1.aspx] 被浏览器的 GET 调用一般。[Page1.aspx] 的处理程序 Page_Load 将正常执行。 我们将利用这一点,在请求上下文中显示由 [Default.aspx] 设置的消息:
using System;
namespace Intro
{
public partial class Page1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Label1.Text = Context.Items["msg1"] as string;
}
}
}
第 9 行显示了 [Default.aspx] 在请求上下文中设置的消息,该消息显示在 Label1 中。
以下是一个执行示例:
![]() |
- 在页面 [Default.aspx] [1] 中,点击链接 [2],该链接将我们引导至页面 Page1
- 在 [3] 中,显示页面 Page1
- 在 [4] 时,显示的是在 [Default.aspx] 中创建并在 [Page1.aspx] 中显示的消息
- 在 [5] 中,浏览器中显示的 URL 是 [Default.aspx] 页面
2.9. 页面重定向
这里介绍另一种功能上与前一种类似的技术:当用户通过 POST 请求页面 [Default.aspx] 时,收到的响应是另一个页面 [Page2.aspx]。 在前一种方法中,用户的请求由两个页面依次处理:[Default.aspx] 和 [Page1.aspx]。而在我们现在介绍的页面重定向方法中,浏览器会发出两个独立的请求:
![]() |
- 在 [1] 页面中,浏览器会向 [Default.aspx] 页面发出 POST 请求。该页面处理请求后,向浏览器发送一个所谓的重定向响应。 该响应是一个简单的 HTTP 数据流(文本行),要求浏览器重定向到另一个 URL [Page2.aspx]。 [Default.aspx] 并未在该初始响应中发送 HTML 数据流。
- 在 [2] 中,浏览器向页面 [Page2.aspx] 发出请求 GET。随后,该页面作为响应发送给浏览器。
- 如果页面 [Default.aspx] 希望向页面 [Page2.aspx] 传递信息,它可以通过用户的会话来实现。与前一种方法不同,此处的请求上下文无法使用,因为这里有两个独立的请求,因此存在两个独立的上下文。 因此必须利用用户会话来实现页面间的通信。
如同之前对 [Page1.aspx] 所做的那样,我们将页面 [Page2.aspx] 添加到项目中:
![]() |
- 在 [1] 中,[Page2.aspx] 已添加到项目中
- 在 [2] 中,[Page2.aspx] 的视觉效果
- 在 [3] 中, 我们在页面 [Default.aspx] 中添加了一个组件 LinkButton [4],该组件将用户重定向至 [Page2.aspx]。
[Page2.aspx] 的源代码与 [Page1.aspx] 的源代码类似:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page2.aspx.cs" Inherits="Intro.Page2" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Page2</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>
Page 2</h1>
<asp:Label ID="Label1" runat="server"></asp:Label>
<br />
<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Retour
vers page [Default]</asp:HyperLink>
</div>
</form>
</body>
</html>
在 [Default.aspx] 中,添加组件 LinkButton 生成了以下源代码:
<asp:LinkButton ID="LinkButtonToPage2" runat="server"
onclick="LinkButtonToPage2_Click">Redirection vers Page2</asp:LinkButton>
[LinkButtonToPage2_Click] 管理器负责重定向至 [Page2.aspx]。其在 [Defaul.aspx.cs] 中的代码如下:
protected void LinkButtonToPage2_Click(object sender, EventArgs e)
{
// 在会话中写入一条消息
Session["msg2"] = "Message de [Default.aspx] pour [Page2.aspx]";
// 将客户端重定向至 [Page2.aspx]
Response.Redirect("Page2.aspx");
}
- 第4行:在用户会话中写入一条消息
- 第5行:对象Response是所有ASPX页面的属性。 它代表了发给客户端的响应。它拥有一个名为 Redirect 的方法,该方法会使发给客户端的响应成为一个 HTTP 重定向指令。
当浏览器接收到重定向到 [Page2.aspx] 的指令时,它将在该页面上执行 GET。 在此页面中,将执行 [Page_Load] 方法。我们将利用该方法从会话中检索 [Default.aspx] 放置的消息并显示出来。[Page2.aspx.cs] 的代码如下:
using System;
namespace Intro
{
public partial class Page2 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// 显示由 [Default.aspx] 存入会话的消息
Label1.Text = Session["msg2"] as string;
}
}
}
执行后,结果如下:
![]() |
- 在 [1] 页面中,点击 [Default.aspx] 的重定向链接。随后生成 POST 并跳转至 [Default.aspx] 页面
- 在 [2] 页面中,浏览器已被重定向至 [Page2.aspx]。这可从浏览器显示的 URL 页面中看出。 在之前的示例中,该URL是[Default.aspx],因为浏览器发出的唯一请求指向的就是该URL。 在此,首先有一个从 POST 指向 [Default.aspx] 的请求,随后在用户不知情的情况下,又有一个从 GET 指向 [Page2.aspx] 的请求。
- 在 [3] 中,可以看到 [Page2.aspx] 已正确检索到 [Default.aspx] 在会话中放置的消息。
2.10. Conclusion
我们通过几个示例介绍了ASP.NET的相关概念,这些概念将在本文后续内容中派上用场。仅凭这一介绍尚无法理解Web应用程序中客户端与服务器之间交互的细微之处。为此,可参阅:
- ASP.NET [Développement WEB avec ASP.NET 1.1 ]



































