Skip to content

2. ASP.NET 简要介绍

本文旨在通过几个示例,介绍本文后续内容中会用到的 ASP.NET 概念。本介绍不涉及 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 指令适用于网页。还有其他指令,如 ApplicationWebService 等,它们适用于其他 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>
          &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>

该页面的[设计]视图如下:

 

[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_InitPage_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 行)。
<!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>
..............................

在“__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 方法时,页面组件的值与用户提交页面时的值相同。因此,这两个 TextBoxText 属性包含用户在浏览器中输入的文本。

以下是一个示例:

  • 在 [1] 中,提交的值
  • 在 [2] 中,服务器的响应。
  • 在 [3] 中,TextBox 控件通过激活的 VIEWSTATE 机制恢复了其提交的值
  • 在 [4] 中,来自 ListBoxEvts 组件的消息源自 Page_InitPage_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:待验证数据的类型。此处,年龄为整数。
  • MinimumValueMaximumValue:被验证的值必须落在该范围之内

触发 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> 标签:

1
2
3
4
5
<select ....>
    <option value="val1">texte1</option>
    <option value="val2">texte2</option>
....
</select>

在 <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] 中,下拉列表提交的值。这对应于列表中已选中 ListItemvalue 属性。
  • 在 [4] 中,即下拉列表。其包含的项目与初始 GET 请求后的内容相同。这是由于 VIEWSTATE 机制所致。

为理解 DropDownListNames 列表的 VIEWSTATE 与 [Default.aspx] 的 Page_Load 处理程序中 if (!IsPostBack) 条件判断之间的交互关系,建议读者采用以下配置重复前面的测试:

情况
DropDownListNames.EnableViewState
[Default.aspx] 的 Page_Load 中的 if(!IsPostBack) 条件判断
1
true
存在
2
false
存在
3
true
缺席
4
false
缺席

各项测试得出以下结果:

  1. 这就是上述情况
  2. 列表在初始 GET 请求期间被填充,但在随后的 POST 请求期间未被填充。由于 EnableViewState 设置为 false,每次 POST 之后列表均为空
  3. 列表在初始 GET 请求后以及随后的 POST 请求期间均被填充。由于 EnableViewState 设置为 true,初始 GET 后有 3 个名字,第一次 POST 后有 6 个名字,第二次 POST 后有 9 个名字,……
  4. 该列表在初始 GET 请求后以及随后的 POST 请求期间均会被填充。由于 EnableViewState 设置为 false,因此无论初始 GET 还是后续 POST 请求,每次请求中列表仅包含 3 个名称。我们观察到与情况 1 相同的行为。因此,有两种方法可以实现相同的结果。

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

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

让我们回顾一下,在POST请求后,ASPX页面上的组件是如何获取其值的:

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

从这一流程中,我们可以推断出:

  • 其值被发布
  • 其值被事件处理程序修改

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 应用程序中客户端/服务器通信的细节。关于这方面,您可以阅读: