Skip to content

2. ASP.NET的简要介绍

本文将通过几个示例,介绍ASP.NET的相关概念,这些概念将在本文后续内容中派上用场。本介绍无法涵盖Web应用程序中客户端/服务器交互的全部细节。如需深入了解,可参阅:

本简介旨在帮助希望快速入门,且暂时愿意忽略某些可能重要细节的读者。文档后续部分将深入探讨这些细节。熟悉 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 适用于网页。还有其他指令,如 ApplicationWebService 等,适用于其他对象 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>
          &nbsp;</td>
      </tr>
      <tr>
        <td>
          Age</td>
        <td>
          <asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
        </td>
        <td>
          &nbsp;</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]按钮时触发

对这三个事件的处理方式是:向名为 ListBoxEvtsListbox 组件添加一条消息。该消息会显示事件发生的时间和事件名称。每条消息都位于列表的开头,因此列表顶部的消息是最新的。

运行该项目后,将显示以下页面:

从 [1] 可以看出,事件 Page_InitPage_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] 页面。
<!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><title>
        Introduction ASP.NET
</title></head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form name="form1" method="post" action="default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTMzMTEyNDMxMg9kFgICAw9kFgICBw8QZBAVAhMwNjoxNjozNjogUGFnZV9Mb2FkEzA2OjE2OjM2OiBQYWdlX0luaXQVAhMwNjoxNjozNjogUGFnZV9Mb2FkEzA2OjE2OjM2OiBQYWdlX0luaXQUKwMCZ2dkZGRW1AnTL8f/q7h2MXBLxctKD1UKfg==" />
</div>
..............................

在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_InitPage_LoadButtonValider_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”。在此情况下,
    • 客户端将执行 EnableClientScriptTrue 之间的验证器。只有当所有客户端验证器均成功时,才会执行 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>

1
2
3
4
5
<select ....>
    <option value="val1">texte1</option>
    <option value="val2">texte2</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] 中,下拉列表的提交值。对应于列表中选中的 ListItemvalue 属性。
  • 在 [4] 中,即下拉列表。它包含与初始 GET 之后相同的元素。这是由 VIEWSTATE 的机制所决定的。

要理解列表 DropDownListNoms 中的 VIEWSTATE 与 Page_Load 管理器中的 if 测试 (! IsPostBack) 之间的交互,建议读者使用以下配置重新进行前面的测试:

情况
DropDownListNoms.EnableViewState
[Default.aspx] 中的 Page_Load 执行 if(! IsPostBack) 测试
1
true
présent
2
false
présent
3
true
absent
4
false
absent

各项测试结果如下:

  1. 这正是上文所述的情况
  2. 列表在初始的 GET 时被填充,但在随后的 POST 中未被填充。由于 EnableViewState 返回 false,因此在每次 POST 之后列表均为空
  3. 列表在初始的 GET 之后以及随后的 POST 过程中均被填充。由于 EnableViewState 对应 vrai, 因此在初始的 GET 之后有 3 个名称,在第一个 POST 之后有 6 个名称,在第二个 POST 之后有 9 个名称,……
  4. 无论是在初始的GET之后,还是在随后的POST中,列表都会被填满。 由于 EnableViewStatefaux 相同,因此无论请求是初始的 GET 还是随后的 POST,列表在每次请求中都仅填充 3 个名称。 这与情况1的行为一致。因此,有两种方法可以获得相同的结果。

2.7. VIEWSTATE对ASPX页面元素的管理

默认情况下,ASPX 页面中的所有元素,其 EnableViewState 属性均设置为 True。每次将 ASPX 页面发送至客户端浏览器时, 该页面将包含一个隐藏字段 __VIEWSTATE,其值为一个字符串,该字符串编码了所有属性值在 EnableViewStateTrue 之间的组件的值。 为了尽量缩小该字符串的大小,可以尝试减少那些 EnableViewStateTrue 属性值的组件数量。

回顾一下页面 ASPX 中的组件如何在 POST 执行完成后获取其值:

  1. ASPX页面被实例化。组件使用其设计值进行初始化。
  2. 使用浏览器提交的 __VIEWSTATE 值,使组件恢复到上次将页面 ASPX 发送至浏览器时的状态。
  3. 将浏览器提交的值赋给组件
  4. 执行事件处理程序。它们可能会修改某些组件的值。

由此流程可推断,以下组件:

  • 其值被提交
  • 其值被事件处理程序修改

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 ]