2. ASP.NET 简要介绍
本文旨在通过几个示例,介绍本文后续内容中会用到的 ASP.NET 概念。本介绍不涉及 Web 应用程序中客户端/服务器通信的复杂细节。关于这方面,您可以阅读:
- 《ASP.NET 编程 [基于 ASP.NET 1.1 的 Web 开发]》
本介绍面向希望快速入门的人士,并接受——至少在初期——某些潜在重要内容可能未作详细说明。本文档的其余部分将更深入地探讨这些要点。熟悉 ASP.NET 的读者可直接跳至第 3 节。
2.1. 示例项目
2.1.1. 创建项目
![]() |
- 在 [1] 中,使用 Visual Web Developer 创建一个新项目
- 在 [2] 中,选择 Visual C# Web 项目
- 在 [3] 中,指定要创建一个 ASP.NET Web 应用程序
- 在 [4] 中,为应用程序命名。系统将以此名称为项目创建一个文件夹。
- 在 [5] 中,指定项目文件夹 [4] 的父文件夹
![]() |
- 在 [6] 中,生成的项目
- [Default.aspx] 是默认生成的网页。它包含 HTML 标签和 ASP.NET 标签
- [Default.aspx.cs] 包含用于处理浏览器中显示的 [Default.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] 可以通过可视化方式([设计] 选项卡)或使用标签([源代码] 选项卡)进行构建
- 在[4]中,[Default.aspx]页面处于[设计]模式。它是通过拖放工具箱[5]中的组件构建而成的。
![]() |
[源代码] 模式 [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] 中的事件与其在 [Default.aspx.cs] 中的处理程序之间的绑定是通过事件名称完成的。因此,[Default.aspx] 页面上的 Load 事件将由 Inherits 属性所定义的 Intro._Default 类的 Page_Load 方法进行处理。
- 第 4–14 行使用标签描述 [Default.aspx] 页面:
- 经典 HTML 标签,例如 <body> 或 <div> 标签
- ASP.NET 标签。这些是带有 runat="server" 属性的标签。ASP.NET 标签会在页面发送给客户端之前由 Web 服务器进行处理,并被转换为 HTML 标签。因此,客户端浏览器接收到的是一份标准 HTML 页面,其中已不再包含任何 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>
</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] 文件声明了 [Default.aspx] 页面的组件:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime version :2.0.50727.3603
//
// Changes made to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Intro {
public partial class _Default {
/// <summary>
/// Control form1.
/// </summary>
/// <remarks>
/// Automatically generated field.
/// To modify, move the field declaration from the designer file to the code-behind file.
/// </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 Web 页面的事件
我们将构建以下 [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>
该页面的[设计]视图如下:
![]() |
[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)
{
// the event
ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
}
protected void Page_Load(object sender, EventArgs e)
{
// the event
ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
}
protected void ButtonValider_Click(object sender, EventArgs e)
{
// the event
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 行),该事件在用户点击 [Validate] 按钮时触发
处理这三个事件中的每一个,都需要向名为 ListBoxEvts 的 Listbox 组件添加一条消息。该消息会显示事件的时间和名称。每条消息都会被放置在列表的顶部。因此,列表顶部的消息是最新的。
运行项目时,将显示以下页面:
![]() |
我们可以从 [1] 中看到,Page_Init 和 Page_Load 事件按此顺序发生。请记住,列表顶部显示的是最近发生的事件。当浏览器通过 URL [2] 直接请求 [Default.aspx] 页面时,它会使用一种名为 GET 的 HTTP(超文本传输协议)命令。页面在浏览器中加载完成后,用户将触发页面上的事件。 例如,用户会点击 [Submit] 按钮 [3]。页面在浏览器中加载后,用户触发的事件会向 [Default.aspx] 页面发起请求,但这次使用的是名为 POST 的 HTTP 命令。总结如下:
- 页面 P 在浏览器中的初始加载是通过 HTTP GET 操作完成的
- 随后在页面上发生的事件每次都会向同一页面 P 生成新的请求,但这次使用的是 HTTP POST 命令。页面 P 可以判断自身是通过 GET 还是 POST 命令被请求的,从而在必要时采取不同的处理方式——而这种情况通常都会发生。
对 ASPX 页面的初始请求:GET
![]() |
- 在[1]中,浏览器通过一个不带参数的HTTP GET请求来请求ASPX页面。
- 在[2]中,Web服务器返回所请求ASPX页面的HTML输出。
处理在浏览器显示的页面上触发的事件:POST
![]() |
- 在[1]中,当HTML页面上发生事件时,浏览器会向之前通过GET操作获取的ASPX页面发送请求,此次使用的是带有参数的HTTP POST请求。这些参数是浏览器显示的HTML页面中位于<form>标签内的组件的值。这些值被称为客户端提交的值。ASPX页面将使用这些值来处理客户端的请求。
- 在[2]中,Web服务器通过POST方式返回最初请求的ASPX页面的HTML输出,若发生页面转移或重定向,则返回其他页面的HTML输出。
让我们回到我们的示例页面:
![]() |
- 在 [2] 中,该页面是通过 GET 请求获取的。
- 在[1]中,我们可以看到在此GET请求过程中发生的两个事件
如果用户点击上方的 [Validate] 按钮 [3],系统将通过 POST 请求调用 [Default.aspx] 页面。该 POST 请求将携带参数,这些参数即 [Default.aspx] 页面中 <form> 标签内所有控件的值:两个 TextBox 控件 [TextBoxName, TextBoxAge]、[SubmitButton] 按钮以及列表控件 [EventListBox]。 各控件的提交值如下:
- TextBox:输入的值
- 按钮:按钮文本,本例中为“Validate”
- ListBox:ListBox 中所选消息的文本
响应此 POST 请求,我们得到页面 [4]。这仍是 [Default.aspx] 页面。除非页面事件处理程序进行了页面转移或重定向,否则这是正常行为。我们可以看到发生了两个新事件:
- Page_Load 事件,该事件在页面加载时触发
- ButtonValider_Click 事件,该事件在点击 [Validate] 按钮时触发
请注意:
- 在 HTTP POST 操作期间,Page_Init 事件不会触发,而在 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 服务器发送的 [Default.aspx] 页面中包含一个名为隐藏字段的 HTML 标签 <input type="hidden" ...>(见下文第 10 行)。
在“__VIEWSTATE”标识符字段中,Web 服务器会编码该页面所有组件的值。无论是初始的 GET 请求还是随后的 POST 请求,都会进行此操作。当向页面 P 发送 POST 请求时:
- 浏览器通过在请求中发送<form>标签内所有组件的值来请求页面 P。在上文中,我们可以看到“__VIEWSTATE”组件位于<form>标签内。因此,其值会在 POST 请求期间发送至服务器。
- 页面 P 通过其构造函数的初始化值被实例化并初始化
- “__VIEWSTATE”组件用于恢复页面 P 先前提交时各组件的值。例如,事件列表 [4] 正是通过这种方式,取回了响应浏览器初始 GET 请求时发送的最初两条消息。
- 随后,页面 P 的组件将采用浏览器提交的值。此时,页面 P 上的表单处于用户提交时的状态。
- 处理 Page_Load 事件。在此,它向事件列表 [4] 中添加一条消息。
- 处理触发 POST 请求的事件。在此,ButtonValider_Click 向事件列表 [4] 中添加一条消息。
- 返回页面 P。各组件具有以下值:
- 要么是提交的值,即组件在表单提交至服务器时的值
- 或者由某个事件处理程序提供的值。
在本例中,
- 两个 TextBox 组件将保留其提交时的值,因为事件处理程序并未修改它们
- 事件列表 [4] 恢复其提交时的值,即已列出的所有事件,加上由 Page_Load 和 ButtonValider_Click 方法创建的两个新事件。
VIEWSTATE 机制可以在组件级别启用或禁用。让我们为 [ListBoxEvts] 组件禁用它:
![]() |
- 在 [1] 中,[ListBoxEvts] 组件的 VIEWSTATE 已被禁用。而 TextBox [2] 的 VIEWSTATE 默认处于启用状态。
- 在 [3] 中,初始 GET 请求后返回的两个事件
![]() |
- 在[4]中,我们已填写表单并点击了[Validate]按钮。系统将向[Default.aspx]页面发送一个POST请求。
- 在[6]中,点击[Validate]按钮后返回的结果
- 启用的 VIEWSTATE 机制解释了为何文本框 [7] 保留了在 [4] 中提交的值
- 禁用 VIEWSTATE 机制解释了为何 [ListBoxEvts] 组件 [8] 未能保留其内容 [5]。
2.3. 处理提交的值
在此,我们将重点关注用户点击 [Validate] 按钮时,两个 TextBox 提交的值。[Design] 模式下的 [Default.aspx] 页面发生如下变化:
![]() |
在[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)
{
// the event
ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
}
protected void Page_Load(object sender, EventArgs e)
{
// the event
ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
}
protected void ButtonValider_Click(object sender, EventArgs e)
{
// the event
ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
// display name and age
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 属性表示组件显示的文本。
- TextBoxName 和 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 代码执行。当用户提交表单中输入的值时,这些值会首先由 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(默认):即使未显示错误消息,验证器也会在页面上占用空间
- 动态:若未显示错误消息,验证器不会占用页面空间。
<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>
- Type:待验证数据的类型。此处,年龄为整数。
- MinimumValue、MaximumValue:被验证的值必须落在该范围之内
触发 POST 请求的组件配置会影响验证模式。此处,该组件是 [Validate] 按钮:
<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)
{
// the event
ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
// display name and age
LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
// is the page valid?
Page.Validate();
if (!Page.IsValid)
{
// global error msg
LabelErreursSaisie.Text = "Veuillez corriger les erreurs de saisie...";
LabelErreursSaisie.Visible = true;
return;
}
// hide error msg
LabelErreursSaisie.Visible = false;
// displays validated name and age
LabelValidation.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
}
如果 [Validate] 按钮的 CausesValidation 属性设置为 True,且验证器的 EnableClientScript 属性设置为 True,则只有当提交的值有效时,ButtonValider_Click 方法才会被执行。那么,人们可能会疑惑第 8 行开始的代码有何作用。需要记住的是,始终有可能编写客户端脚本,将未经验证的值提交到 [Default.aspx] 页面。 因此,该页面必须始终重新执行有效性检查。
- 第 8 行:触发页面上所有验证器的执行。如果 [Validate] 按钮的 CausesValidation 属性设置为 True,则此操作会自动执行,无需重复。此处存在冗余。
- 第 9–15 行:其中一个验证器失败的情况
- 第 16–19 行:所有验证器均通过的情况
以下是两个执行示例:
![]() |
- 在 [1] 中,给出了以下情况下的执行示例:
- [Validate] 按钮的 CausesValidation 属性设置为 True
- 验证器的 EnableClientScript 属性设置为 True
这些错误消息 [2] 是由页面 JavaScript 代码在客户端执行的验证器显示的。如已提交元素的标签 [3] 所示,未向服务器发送任何 POST 请求。
- 在 [4] 中,以下情况的执行示例:
- [Validate] 按钮的 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。
这里我们关注的是应用程序范围的数据,该数据由所有用户共享。应用程序的全局类可以如下创建:
![]() |
- 在 [1] 中,向项目添加一个新元素
- 在 [2] 中,添加全局应用程序类
- 在 [3] 中,为新元素保留默认名称 [Global.asax]
![]() |
- 在 [4] 中,项目中已添加了两个新文件
- 在 [5] 中,显示 [Global.asax] 的标记
<%@ Application Codebehind="Global.asax.cs" Inherits="Intro.Global" Language="C#" %>
- Application 标签取代了 [Default.aspx] 中使用的 Page 标签。它标识了全局应用程序类
- Codebehind:定义包含全局应用程序类的文件
- 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_AuthenticateRequest 事件(用户通过身份验证时发生)。
[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)
{
// the event
ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
// retrieve information from the global application class
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 服务器能够识别用户。
- 会话具有超时期限。当 Web 服务器接收到用户的请求时,会计算自上次请求以来经过的时间。如果该时间超过会话超时时间,则会为用户创建一个新的会话。前一个会话的数据将丢失。在 Microsoft 的 IIS(Internet Information Server)Web 服务器中,会话的默认生命周期为 20 分钟。此值可由 Web 服务器管理员进行更改。
- Web 服务器能够识别正在处理用户的首次请求,因为该请求不包含会话令牌。这是唯一的请求。
任何 ASP.NET 页面均可通过页面的 Session 属性(类型为 [System.Web.SessionState.HttpSessionState])访问用户的会话。我们将使用 HttpSessionState 类的以下 P 属性与 M 方法:
名称 | 类型 | Role |
Item[String key] | P | 会话可以采用字典结构。Item[key] 表示由 key 标识的会话元素。除了写 [HttpSessionState].Item[key] 之外,也可以写 [HttpSessionState].[key]。 |
清除 | M | 清除会话字典 |
放弃 | M | 结束会话。此后该会话将不再有效。用户下一次请求时将启动新的会话。 |
作为用户状态的一个示例,我们将统计用户点击 [提交] 按钮的次数。要实现这一点,我们需要在用户的会话中维护一个计数器。
[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)
{
// query counter
Session["nbRequêtes"] = 0;
}
}
}
在第 19 行,我们使用用户的会话来存储一个请求计数器,该计数器由键“nbRequests”标识。该计数器由 [Default.aspx] 页面上的 [ButtonValider_Click] 处理程序进行更新:
using System;
namespace Intro
{
public partial class _Default : System.Web.UI.Page
{
....
protected void ButtonValider_Click(object sender, EventArgs e)
{
// the event
ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
// post name and age are displayed
LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
// number of requests
Session["nbRequêtes"] = (int)Session["nbRequêtes"] + 1;
LabelNbRequetes.Text = Session["nbRequêtes"].ToString();
// is the page valid?
Page.Validate();
if (!Page.IsValid)
{
...
}
...
}
}
}
- 第 16 行:递增请求计数器
- 第 17 行:在页面上显示计数器
以下是一个执行示例:
![]() |
2.6. 在页面加载时处理 GET / POST 请求
我们提到,对 ASPX 页面的请求主要有两种类型:
- 浏览器发出的初始请求,使用 HTTP GET 命令。服务器通过发送所请求的页面进行响应。我们将假设该页面是一个表单,即发送的 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 的字符串值。我们将稍后定义“值”这一概念。 |
用于下拉列表中项目的 ListItem 类,用于在 HTML <select> 标签内生成 <option> 标签:
在 <option> 标签中
- text1 是下拉列表中显示的文本
- vali 是浏览器提交的值,如果 texti 是下拉列表中选中的文本
每个选项均可通过使用 ListItem(string text, string value) 构造函数创建的 ListItem 对象生成。
在 [Default.aspx.cs] 中,[Page_Load] 处理程序的代码更改如下:
protected void Page_Load(object sender, EventArgs e)
{
// the event
...
// retrieve information from the global application class
...
// initialization of name combo only during initial 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 行:我们将一个类型为 ListItem(string text, string value) 的元素添加到 [DropDownListNames] 列表中。第 (i+1) 个元素显示的文本为 "names",如果该元素被选中,则提交的值为 i。
修改 [ButtonValider_Click] 处理程序以显示下拉列表提交的值:
protected void ButtonValider_Click(object sender, EventArgs e)
{
// the event
...
// display posted values
LabelPost.Text = string.Format("nom={0}, age={1}, combo={2}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim(), DropDownListNoms.SelectedValue);
// number of requests
...
}
第 6 行:通过列表的 SelectedValue 属性获取 [DropDownListNames] 列表的提交值。以下是一个执行示例:
![]() |
- 在 [1] 中,初始 GET 请求后、首次 POST 请求前下拉列表的内容
- [2] 处,为首次 POST 请求后的页面。
- 在 [3] 中,下拉列表提交的值。这对应于列表中已选中 ListItem 的 value 属性。
- 在 [4] 中,即下拉列表。其包含的项目与初始 GET 请求后的内容相同。这是由于 VIEWSTATE 机制所致。
为理解 DropDownListNames 列表的 VIEWSTATE 与 [Default.aspx] 的 Page_Load 处理程序中 if (!IsPostBack) 条件判断之间的交互关系,建议读者采用以下配置重复前面的测试:
情况 | DropDownListNames.EnableViewState | [Default.aspx] 的 Page_Load 中的 if(!IsPostBack) 条件判断 |
各项测试得出以下结果:
- 这就是上述情况
- 列表在初始 GET 请求期间被填充,但在随后的 POST 请求期间未被填充。由于 EnableViewState 设置为 false,每次 POST 之后列表均为空
- 列表在初始 GET 请求后以及随后的 POST 请求期间均被填充。由于 EnableViewState 设置为 true,初始 GET 后有 3 个名字,第一次 POST 后有 6 个名字,第二次 POST 后有 9 个名字,……
- 该列表在初始 GET 请求后以及随后的 POST 请求期间均会被填充。由于 EnableViewState 设置为 false,因此无论初始 GET 还是后续 POST 请求,每次请求中列表仅包含 3 个名称。我们观察到与情况 1 相同的行为。因此,有两种方法可以实现相同的结果。
2.7. 管理 ASPX 页面上元素的 VIEWSTATE
默认情况下,ASPX 页面上的所有元素其 EnableViewState 属性均设置为 True。每次将 ASPX 页面发送至客户端浏览器时,页面中都会包含一个名为 __VIEWSTATE 的隐藏字段,其值是一个字符串,该字符串编码了所有 EnableViewState 属性设置为 True 的组件的值。为了尽量减小该字符串的大小,我们可以尝试减少那些 EnableViewState 属性设置为 True 的组件数量。
让我们回顾一下,在POST请求后,ASPX页面上的组件是如何获取其值的:
- ASPX 页面被实例化。组件使用其设计值进行初始化。
- 使用浏览器提交的 __VIEWSTATE 值,将组件恢复为上次 ASPX 页面发送至浏览器时的状态。
- 浏览器提交的值被赋给控件。
- 事件处理程序被执行。它们可能会修改某些控件的值。
从这一流程中,我们可以推断出:
- 其值被发布
- 其值被事件处理程序修改
其 EnableViewState 属性可能被设置为 False,因为其 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 属性值可以如下所示:
组件 | 已发布值 | EnableViewState | 原因 |
TextBoxName | 在 TextBox 中输入的值 | False | 组件的值被提交 |
TextBoxAge | 相同 | ||
必填字段验证器名称 | 无 | 错误 | 不支持组件值的概念 |
RequiredFieldValidatorAge | 相同 | ||
RangeValidatorAge | 相同 | ||
标签发布 | 无 | false | 其值来自事件处理程序 |
LabelValidation | 相同 | ||
输入错误标签 | 相同 | ||
全局标签 | 相同 | ||
标签请求数 | 相同 | ||
下拉列表名称 | 所选项的“value” | True | 我们希望在跨请求时保留列表内容,而无需重新生成它 |
ListBoxEvts | 所选项的“value” | False | 列表内容由事件处理程序生成 |
ButtonValidate | 按钮标签 | 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":点击该链接将向 [Default.aspx] 触发一个 POST 请求。[LinkButton] 组件的行为与 [Button] 组件相同。在此,我们不希望点击链接触发验证器的执行。
- EnableViewState="False":无需在请求之间保留链接的状态。它将保留其设计值。
- onclick="LinkButtonToPage1_Click":在 [Defaul.aspx.cs] 中处理 LinkButtonToPage1 组件 Click 事件的方法名称。
LinkButtonToPage1_Click 处理程序的代码如下:
// to Page1
protected void LinkButtonToPage1_Click(object sender, EventArgs e)
{
// we put information in context
Context.Items["msg1"] = "Message de Default.aspx pour Page1";
// pass the request to 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 字典。
当通过 Server.Transfer("Page1.aspx", true) 操作加载 [Page1.aspx] 时,其运行过程与 [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] 页面的 URL
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] 的操作。其在 [Default.aspx.cs] 中的代码如下:
protected void LinkButtonToPage2_Click(object sender, EventArgs e)
{
// put a msg in the session
Session["msg2"] = "Message de [Default.aspx] pour [Page2.aspx]";
// the client is redirected to [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)
{
// displays the msg set in the session by [Default.aspx]
Label1.Text = Session["msg2"] as string;
}
}
}
执行后,得到以下结果:
![]() |
- 在 [1] 中,我们点击 [Default.aspx] 上的重定向链接。一个 POST 请求被发送至 [Default.aspx] 页面
- 在[2]中,浏览器已被重定向至[Page2.aspx]。这可从浏览器显示的URL中看出。在前一种方法中,该URL是[Default.aspx]的,因为浏览器发出的唯一请求就是针对该URL的。而在这里,首先向[Default.aspx]发送了一个POST请求,随后又在用户不知情的情况下向[Page2.aspx]发送了第二个GET请求。
- 在 [3] 中,我们可以看到 [Page2.aspx] 已正确检索到 [Default.aspx] 放置在会话中的消息。
2.10. 结论
我们通过几个示例介绍了本文后续内容中将用到的 ASP.NET 概念。本介绍并未涵盖 Web 应用程序中客户端/服务器通信的细节。关于这方面,您可以阅读:
- 《ASP.NET 编程 [基于 ASP.NET 1.1 的 Web 开发]》



































