65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET 框架概述

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (2投票s)

2010 年 11 月 1 日

CPOL

50分钟阅读

viewsIcon

25033

摘自 ASP.NET 4 Unleashed

ASP.NET 4 Unleashed

Stephen Walther, Kevin Hoffman, Nate Dudek
由Sams出版
ISBN-10: 0-672-33112-8
ISBN-13: 978-0-672-33112-1

本章内容

  • ASP.NET 与 .NET Framework
  • 理解 ASP.NET 控件
  • 理解 ASP.NET 页面
  • 安装 ASP.NET
  • 摘要

让我们开始构建一个简单的 ASP.NET 页面。

注意 - 有关安装 ASP.NET 的信息,请参阅本章的最后一节。

如果您使用 Visual Web Developer 或 Visual Studio,首先需要创建一个新的网站。启动 Visual Web Developer,然后选择“文件”->“新建网站”。此时会显示“新建网站”对话框(参见图 1.1)。在“位置”字段中输入要创建新网站的文件夹(例如“Chapter1”),然后单击“确定”按钮。

图 1.1 创建新网站。

注意 - 创建新网站时,您可能会收到一条错误消息,警告您需要在 Internet Explorer 中启用脚本调试。您需要启用脚本调试才能构建 Ajax 应用程序。我们将在本书稍后讨论 Ajax。

创建新网站后,您可以向其添加 ASP.NET 页面。选择“网站”->“添加新项”。选择“Web 窗体”,然后在“名称”字段中输入值FirstPage.aspx。请确保取消选中“将代码放在单独的文件中”和“选择母版页”复选框,然后单击“添加”按钮创建新的 ASP.NET 页面(参见图 1.2)。

图 1.2 添加新的 ASP.NET 页面。

请确保您的 FirstPage.aspx 代码与清单 1.1 中的代码一致。

清单 1.1   FirstPage.aspx

<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    void Page_Load()
    {
        lblServerTime.Text = DateTime.Now.ToString();
    }
 
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head>
    <title>First Page</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    Welcome to ASP.NET 4.0! The current date and time is:
 
    <asp:Label
        id=”lblServerTime”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

注意 - 本书的网站包含本书中所有代码示例的 C# 和 VB.NET 版本。

清单 1.1 中的 ASP.NET 页面显示一条简短的消息以及服务器的当前日期和时间。您可以通过右键单击页面并选择“在浏览器中查看”(参见图 1.3)来在浏览器中查看清单 1.1 中的页面。

图 1.3 在浏览器中查看 FirstPage.aspx。

清单 1.1 中的页面是一个极其简单的页面。但是,它确实说明了 ASP.NET 页面的最常见元素。该页面包含一个指令、一个代码声明块和一个页面呈现块。

清单 1.1 中的第一行包含一个指令,其形式如下:

<%@ Page Language=”C#” %>

指令始终以特殊字符 <%@ 开头,以 %> 字符结尾。指令主要用于向编译器提供编译页面所需的信息。

例如,清单 1.1 中的指令表明页面中包含的代码是 C# 代码。页面由 C# 编译器编译,而不是其他编译器,例如 Visual Basic .NET (VB.NET) 编译器。

页面的下一部分以开头的 <script runat=”server”> 标签开始,以结束的 </script> 标签结束。<script> 标签包含代码声明块。

代码声明块包含页面中使用的所有方法。它包含页面中的所有函数和子例程。清单 1.1 中的代码声明块包含一个名为 Page_Load() 的单个方法,其形式如下:

void Page_Load()
{
    lblServerTime.Text = DateTime.Now.ToString();
}

此方法将当前日期和时间分配给页面正文中名为 lblServerTime 的 Label 控件的 Text 属性。

Page_Load() 方法是事件处理程序的示例。此方法处理 Page Load 事件。每次加载页面时,该方法都会自动执行,并将当前日期和时间分配给 Label 控件。

页面的最后一部分称为页面呈现块,其中包含呈现给浏览器的所有内容。在清单 1.1 中,呈现块包括 <html> 标签开头和结尾之间的所有内容。

页面呈现块的大部分由日常 HTML 组成。例如,页面包含标准的 HTML <head><body> 标签。在清单 1.1 中,页面呈现块中包含两件特殊事情。

首先,请注意页面包含一个 <form> 标签,其形式如下:

<form id=”form1” runat=”server”>

这是 ASP.NET 控件的一个示例。由于该标签包含 runat=”server” 属性,因此该标签代表在服务器上执行的 ASP.NET 控件。

ASP.NET 页面通常称为 Web 窗体页面,因为它们几乎总是包含服务器端表单元素。

页面呈现块还包含一个 Label 控件。Label 控件使用 <asp:Label> 标签声明。在清单 1.1 中,Label 控件用于显示当前日期和时间。

控件是 ASP.NET 框架的核心。本书的大部分篇幅都致力于描述 ASP.NET 控件的属性和功能。控件稍后将详细讨论;但在此之前,您需要了解 .NET Framework。

注意 - 默认情况下,ASP.NET 页面与 XHTML 1.0 Transitional 标准兼容。清单 1.1 中的页面包含一个 XHTML 1.0 Transitional DOCTYPE。有关 ASP.NET Framework 如何符合 XHTML 和可访问性标准的详细信息,请参阅 Microsoft MSDN 网站 (msdn.Microsoft.com) 上的文章“使用 Web 标准构建 ASP.NET 2.0 网站”。

ASP.NET 与 .NET Framework

ASP.NET 是 Microsoft .NET Framework 的一部分。要构建 ASP.NET 页面,您需要利用 .NET Framework 的功能,它由两部分组成:Framework 类库和通用语言运行时。

理解 Framework 类库

.NET Framework 包含数千个您可以在构建应用程序时使用的类。Framework 类库旨在更轻松地执行最常见的编程任务。以下是框架中类的几个示例:

  • File 类 — 使您能够表示硬盘上的文件。您可以使用 File 类检查文件是否存在、创建新文件、删除文件以及执行许多其他与文件相关的任务。
  • Graphics 类 — 使您能够处理不同类型的图像,如 GIF、PNG、BMP 和 JPEG。您可以使用 Graphics 类在图像上绘制矩形、圆弧、椭圆和其他元素。
  • Random 类 — 使您能够生成随机数。
  • SmtpClient 类 — 使您能够发送电子邮件。您可以使用 SmtpClient 类发送包含附件和 HTML 内容的电子邮件。

框架仅包含四类示例。 .NET Framework 包含超过 13,000 个类,您可以在构建应用程序时使用它们。

您可以通过打开 Microsoft .NET Framework SDK 文档(位于 Microsoft 的 .NET Framework 开发人员中心网站)并展开“类库”节点(参见图 1.4)来查看框架中包含的所有类。SDK 文档网站位于 http://msdn.microsoft.com/en-us/netframework/default.aspx

图 1.4 在线 Microsoft .NET Framework SDK 文档。

Framework 中的每个类都可以包含属性、方法和事件。类公开的属性、方法和事件是类的成员。例如,以下是 SmtpClient 类成员的部分列表:

  • 属性
    • Host — 您的电子邮件服务器的名称或 IP 地址
    • Port — 发送电子邮件消息时使用的端口号
  • 方法
    • Send — 使您能够同步发送电子邮件消息
    • SendAsync — 使您能够异步发送电子邮件消息
  • 事件
    • SendCompleted — 异步发送操作完成后引发

如果您知道类的成员,那么您就知道可以对类执行的所有操作。例如,SmtpClient 类包含两个名为 HostPort 的属性,它们允许您在发送电子邮件消息时指定要使用的电子邮件服务器和端口。

SmtpClient 类还包含两个方法,您可以使用它们发送电子邮件:Send()SendAsync()Send 方法会阻塞进一步的程序执行,直到发送操作完成。而 SendAsync() 方法则异步发送电子邮件。与 Send() 方法不同,SendAsync() 方法不会等待检查发送操作是否成功。

最后,SmtpClient 类包含一个名为 SendCompleted 的事件,该事件在异步发送操作完成时引发。您可以为 SendCompleted 事件创建一个事件处理程序,该处理程序在电子邮件成功发送时显示一条消息。

清单 1.2 中的页面使用 SmtpClient 类并通过调用其 Send() 方法发送电子邮件。

清单 1.2   SendMail.aspx

<%@ Page Language=”C#” %>
<%@ Import Namespace=”System.Net.Mail” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    void Page_Load()
    {
        SmtpClient client = new SmtpClient();
        client.Host = “localhost”;
        client.Port = 25;
        client.Send(“nate@somewhere”, “nate@exclaimcomputing.com”,
           “Beware!”, “Watch out for zombies!”);
    }
 
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
    <title>Send Mail</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
    Email sent!
 
    </div>
    </form>
</body>
</html>

清单 1.2 中的页面调用 SmtpClient Send() 方法来发送电子邮件。第一个参数是发件人地址;第二个参数是收件人地址;第三个参数是主题;最后一个参数是电子邮件的正文。

警告 - 清单 1.2 中的页面通过使用本地 SMTP 服务器发送电子邮件。如果您的 SMTP 服务器未启用,您将收到错误“远程主机强制关闭了现有连接”。您可以通过打开 Internet 信息服务,右键单击“默认 SMTP 虚拟服务器”,然后选择“启动”来启用本地 SMTP 服务器。

理解命名空间

.NET Framework 中有超过 13,000 个类。这是一个庞大的数字。如果 Microsoft 只是将所有类混在一起,您将永远找不到任何东西。幸运的是,Microsoft 将框架中的类分成了单独的命名空间。

命名空间 仅仅是一个类别。例如,所有与文件系统相关类的类都位于 System.IO 命名空间中。所有与 Microsoft SQL Server 数据库相关的类的类都位于 System.Data.SqlClient 命名空间中。

在页面中使用类之前,必须指明与该类相关的命名空间。有多种方法可以做到这一点。

首先,您可以完全限定一个类名及其命名空间。例如,由于 File 类位于 System.IO 命名空间中,因此您可以使用以下语句来检查文件是否存在:

System.IO.File.Exists(“SomeFile.txt”)

每次使用类时都指定命名空间可能会很快变得乏味。(这涉及到大量的输入。)第二个选项是导入命名空间。

您可以在页面中添加 <%@ Import %> 指令来导入特定命名空间。在清单 1.2 中,我们导入了 System.Net.Mail 命名空间,因为 SmtpClient 是该命名空间的一部分。清单 1.2 中的页面在页面顶部附近包含以下指令:

<%@ Import Namespace=”System.Net.Mail” %>

导入特定命名空间后,您就可以使用该命名空间中的所有类,而无需限定类名。

最后,如果您发现您的应用程序中的多个页面都使用了某个命名空间,您可以配置应用程序中的所有页面以识别该命名空间。

注意 - Web.config 文件是一种特殊类型的文件,您可以将其添加到应用程序中以配置您的应用程序。请注意,该文件是 XML 文件,因此其中包含的所有标签都区分大小写。您可以通过选择“网站”->“添加新项”->“Web.config 文件”将 Web.config 文件添加到您的应用程序。第 34 章“配置应用程序”将详细讨论 Web.config 文件。

如果将清单 1.3 中的 Web.config 文件添加到您的应用程序,则无需在页面中导入 System.Net.Mail 命名空间即可使用该命名空间中的类。例如,如果将 Web.config 文件包含在您的项目中,则可以从清单 1.2 的页面中删除 <%@ Import %> 指令。

清单 1.3   Web.Config

<?xml version=”1.0”?>
<configuration>
    <system.web>
      <pages>
        <namespaces>
          <add namespace=”System.Net.Mail”/>
        </namespaces>
      </pages>
    </system.web>
</configuration>

您不必导入所有命名空间。ASP.NET 会免费为您提供最常用的命名空间:

  • 系统
  • System.Collections
  • System.Collections.Generic
  • System.Collections.Specialized
  • System.ComponentModel.DataAnnotations
  • System.Configuration
  • System.Data.Entity.Linq
  • System.Data.Linq
  • System.Text
  • System.Text.RegularExpressions
  • System.Web
  • System.Web.Caching
  • System.Web.DynamicData
  • System.Web.SessionState
  • System.Web.Security
  • System.Web.Profile
  • System.Web.UI
  • System.Web.UI.WebControls
  • System.Web.UI.WebControls.WebParts
  • System.Web.UI.HtmlControls
  • System.Xml.Linq

默认命名空间列在位于以下路径的根 Web.config 文件中的 pages 元素内:

\Windows\Microsoft.NET\Framework\v4.0.30128\Config\Web.Config

理解程序集

程序集是硬盘上实际的 .dll 文件,其中存储了 .NET Framework 中的类。例如,ASP.NET Framework 中包含的所有类都位于名为 System.Web.dll 的程序集中。

更准确地说,程序集是 .NET Framework 中部署、安全和版本控制的主要单元。由于程序集可以跨越多个文件,因此程序集通常被称为“逻辑”DLL。

程序集有两种类型:私有和共享。私有程序集只能由单个应用程序使用。而共享程序集可以被位于同一服务器上的所有应用程序使用。

共享程序集位于全局程序集缓存 (GAC) 中。例如,System.Web.dll 程序集以及 .NET Framework 随附的所有其他程序集都位于全局程序集缓存中。

注意 - 全局程序集缓存物理上位于您计算机的 \WINDOWS\Assembly 文件夹中。

在应用程序中使用程序集中的类之前,必须添加对该程序集的引用。默认情况下,ASP.NET 4 应用程序引用全局程序集缓存中最常用的程序集:

  • mscorlib.dll
  • Microsoft.CSharp
  • System.dll
  • System.Configuration.dll
  • System.Web.dll
  • System.Data.dll
  • System.Web.Services.dll
  • System.Xml.dll
  • System.Drawing.dll
  • System.EnterpriseServices.dll
  • System.IdentityModel.dll
  • System.Runtime.Serialization.dll
  • System.ServiceModel.dll
  • System.ServiceModel.Activation.dll
  • System.ServiceModel.Web.dll
  • System.Activities.dll
  • System.ServiceModel.Activities.dll
  • System.WorkflowServices.dll
  • System.Core.dll
  • System.Web.Extensions.dll
  • System.Data.DataSetExtensions.dll
  • System.Xml.Linq.dll
  • System.ComponentModel.DataAnnotations.dll
  • System.Web.DynamicData.dll
  • System.Data.Entity.dll
  • System.Web.Entity.dll
  • System.Data.Linq.dll
  • System.Data.Entity.Design.dll
  • System.Web.ApplicationServices.dll

所有这些程序集都是 .NET 4 Framework 的一部分。基于早期 .NET 版本创建的网站会引用不同的程序集。

注意 - 您可以将网站定位到 .NET Framework 2.0、.NET Framework 3.0、.NET Framework 3.5 或 .NET Framework 4。在 Visual Web Developer 中,选择“网站”->“启动选项”,然后选择“生成”选项卡。您可以从下拉列表中选择要定位的框架。

要使用 .NET Framework 中的任何特定类,您必须执行两件事。首先,您的应用程序必须引用包含该类的程序集。其次,您的应用程序必须导入与该类相关的命名空间。

在大多数情况下,您不必担心引用必要的程序集,因为最常用的程序集会自动引用。但是,如果您需要使用专用程序集,则需要显式地向该程序集添加引用。例如,如果您需要通过 System.DirectoryServices 命名空间中的类与 Active Directory 进行交互,则需要将 System.DirectoryServices.dll 程序集添加到您的应用程序中。

.NET Framework SDK 文档中的每个类条目都列出了与该类相关的程序集和命名空间。例如,如果您在文档中查找 MessageQueue 类,您会发现该类位于 System.Messaging 命名空间中,而该命名空间又位于 System.Messaging.dll 程序集中。

如果您使用 Visual Web Developer,可以通过选择“网站”->“添加引用”并选择您需要引用的程序集的名称来显式添加对程序集的引用。例如,添加对 System.Messaging.dll 程序集的引用将导致清单 1.4 中的 Web.config 文件被添加到您的应用程序中。

清单 1.4   Web.Config

<?xml version=”1.0”?>
<configuration>
<system.web>
  <compilation>
  <assemblies>
  <add
    assembly=”System.Messaging, Version=4.0.0.0,
    Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A”/>
  </assemblies>
  </compilation>
</system.web>
</configuration>

如果您不想使用 Visual Web Developer,可以通过手动创建清单 1.4 中的文件来添加对 System.Messaging.dll 程序集的引用。

理解通用语言运行时 (CLR)

.NET Framework 的第二部分是通用语言运行时 (CLR)。CLR 负责执行您的应用程序代码。

当您使用 C# 或 Visual Basic .NET 等语言为 .NET Framework 编写应用程序时,您的源代码永远不会直接编译成机器代码。相反,C# 或 Visual Basic 编译器会将您的代码转换为一种名为 Microsoft Intermediate Language (MSIL) 的特殊语言。

MSIL 看起来像面向对象的汇编语言;但是,与典型的汇编语言不同,它不是 CPU 特定的。MSIL 是一种低级且与平台无关的语言。

当您的应用程序实际执行时,MSIL 代码会被 JITTER(即时编译器)即时编译为机器代码。通常,您的整个应用程序不会从 MSIL 编译成机器代码。相反,只有在执行过程中实际调用的方法才会被编译。

实际上,.NET Framework 只理解一种语言:MSIL。但是,您可以使用 Visual Basic .NET 和 C# 等语言为 .NET Framework 编写应用程序,因为 .NET Framework 包含这些语言的编译器,允许您将代码编译为 MSIL。

您可以使用数十种不同的语言之一来编写 .NET Framework 的代码,包括:

  • Ada
  • Apl
  • Caml
  • COBOL
  • Eiffel
  • Forth
  • Fortran
  • JavaScript
  • Oberon
  • PERL
  • Pascal
  • PHP
  • Python
  • RPG
  • Ruby
  • Scheme
  • Small Talk

绝大多数构建 ASP.NET 应用程序的开发人员都使用 C# 或 Visual Basic .NET 编写应用程序。前列表中列出的许多其他 .NET 语言都是学术实验。曾几何时,如果您想成为一名开发人员,您会专注于精通某种特定语言。例如,您会成为一名 C++ 程序员、COBOL 程序员或 Visual Basic 程序员。

但是,对于 .NET Framework 来说,掌握特定语言并不特别重要。使用哪种语言构建 .NET 应用程序的选择在很大程度上是偏好选择。如果您喜欢区分大小写和花括号,则应使用 C# 编程语言。如果您想在大小写方面更随意,并且不喜欢分号,那么请使用 Visual Basic .NET 编写代码。

.NET Framework 中所有真正的活动都发生在 Framework 类库中。如果您想成为一名优秀的有 Microsoft 技术知识的程序员,您需要学习如何使用框架中包含的 13,000 个类的所有方法、属性和事件。从 .NET Framework 的角度来看,无论您是从 Visual Basic .NET 还是 C# 应用程序中使用这些类,都没有区别。

注意 - 本书中的所有代码示例都同时使用 C# 和 Visual Basic 编写。所有代码示例都可以在本书网站上找到。

理解 ASP.NET 控件

ASP.NET 控件是 ASP.NET 框架的核心。ASP.NET 控件是在服务器上执行并将特定内容呈现给浏览器的 .NET 类。例如,在本章开头创建的第一个 ASP.NET 页面中,使用 Label 控件显示当前日期和时间。ASP.NET 框架包含 90 多个控件,这些控件使您能够完成从显示数据库记录列表到显示随机旋转的广告横幅的各种操作。

本节概述了 ASP.NET 框架中包含的控件。您还将学习如何处理控件引发的事件以及如何利用视图状态。

ASP.NET 控件概述

ASP.NET 框架包含 90 多个控件。这些控件可以分为七组:

  • 标准控件 — 使您能够呈现标准的表单元素,如按钮、输入字段和标签。我们将在第 2 章“使用标准控件”中详细介绍这些控件。
  • 验证控件 — 使您能够在将表单数据提交到服务器之前对其进行验证。例如,您可以使用 RequiredFieldValidator 控件来检查用户是否为必填输入字段输入了值。这些控件将在第 3 章“使用验证控件”中进行讨论。
  • 丰富控件 — 使您能够呈现日历、文件上传按钮、旋转广告横幅和多步向导等内容。第 4 章“使用丰富控件”将讨论这些控件。
  • 数据控件 — 使您能够处理数据,如数据库数据。例如,您可以使用这些控件将新记录提交到数据库表或显示数据库记录列表。第三部分“执行数据访问”将讨论这些控件。
  • 导航控件 — 使您能够显示标准的导航元素,如菜单、树形视图和面包屑导航。第 22 章“使用导航控件”将讨论这些控件。
  • 登录控件 — 使您能够显示登录、更改密码和注册表单。第 26 章“使用登录控件”将讨论这些控件。
  • HTML 控件 — 使您能够将任何 HTML 标签转换为服务器端控件。本节将讨论这组控件。

除了 HTML 控件之外,您在页面中声明和使用所有 ASP.NET 控件的方式完全相同。例如,如果您想在页面中显示文本输入字段,可以像这样声明一个 TextBox 控件:

<asp:TextBox id=”TextBox1” runat=”Server” />

此控件声明看起来像 HTML 标签的声明。但请记住,与 HTML 标签不同,控件是在服务器上执行的 .NET 类,而不是在 Web 浏览器中。

当 TextBox 控件呈现到浏览器时,它会呈现以下内容:

<input name=”TextBox1” type=”text” id=”TextBox1” />

控件声明的第一部分,即 asp: 前缀,表示控件的命名空间。所有标准的 ASP.NET 控件都包含在 System.Web.UI.WebControls 命名空间中。asp: 前缀代表此命名空间。

接下来,声明包含正在声明的控件的名称。在这种情况下,声明了一个 TextBox 控件。

此声明还包含一个 ID 属性。您使用 ID 在页面中的代码中引用该控件。每个控件都必须有一个唯一的 ID。

注意 - 即使您不需要对其进行编程,也应始终为每个控件分配 ID 属性。如果您不提供 ID 属性,ASP.NET 框架的某些功能(例如双向数据绑定)将无法正常工作。

声明还包含一个 runat=”Server” 属性。此属性将该标签标记为代表服务器端控件。如果您忽略此属性,TextBox 标签将在不执行的情况下被传递到浏览器。浏览器将简单地忽略该标签。

最后,请注意标签以斜杠结尾。斜杠是创建闭合 </asp:TextBox> 标签的简写。如果您愿意,也可以像这样声明 TextBox 控件:

<asp:TextBox id=”TextBox1” runat=”server”></asp:TextBox>

在这种情况下,开始标签不包含斜杠,并包含显式结束标签。

理解 HTML 控件

您声明 HTML 控件的方式与声明标准 ASP.NET 控件不同。ASP.NET 框架使您可以采用任何 HTML 标签(真实或虚拟)并为其添加 runat=”server” 属性。runat=”server” 属性会将 HTML 标签转换为服务器端 ASP.NET 控件。

例如,清单 1.5 中的页面包含一个 <span> 标签,该标签已被转换为 ASP.NET 控件。

清单 1.5   HtmlControls.aspx

<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    void Page_Load()
    {
        spanNow.InnerText = DateTime.Now.ToString(“T”);
    }
 
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
    <title>HTML Controls</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    At the tone, the time will be:
    <span id=”spanNow” runat=”server” />
 
    </div>
    </form>
</body>
</html>

清单 1.5 中的 <span> 标签看起来就像一个普通的 HTML <span> 标签,只是添加了 runat=”server” 属性。

由于清单 1.5 中的 <span> 标签是服务器端 HTML 控件,因此您可以对其进行编程。在清单 1.5 中,当前日期和时间被分配给 Page_Load() 方法中的 <span> 标签。

HTML 控件包含在 ASP.NET 框架中,以方便将现有 HTML 页面转换为使用 ASP.NET 框架。我在本书中很少使用 HTML 控件,因为通常情况下,标准 ASP.NET 控件提供了所有相同的功能,甚至更多。

理解和处理控件事件

大多数 ASP.NET 控件都支持一个或多个事件。例如,ASP.NET Button 控件支持 Click 事件。在单击浏览器中由 Button 控件呈现的按钮后,会在服务器上引发 Click 事件。

清单 1.6 中的页面说明了如何编写在用户单击 Button 控件呈现的按钮时执行的代码(换句话说,它说明了如何创建 Click 事件处理程序)。

清单 1.6   ShowButtonClick.aspx

<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        Label1.Text = “Thanks!”;
    }
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
    <title>Show Button Click</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    <asp:Button
        id=”btnSubmit”
        Text=”Click Here”
        OnClick=”btnSubmit_Click”
        Runat=”server” />
 
    <br /><br />
 
    <asp:Label
        id=”Label1”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

请注意,清单 1.6 中的 Button 控件包含一个 OnClick 属性。此属性指向一个名为 btnSubmit_Click() 的子例程。btnSubmit_Click() 子例程是 Button Click 事件的处理程序。每当单击按钮时,此子例程都会执行(参见图 1.5)。

图 1.5 引发 Click 事件。

当使用 Visual Web Developer 时,有多种方法可以自动向控件添加事件处理程序。在“源代码”视图中,通过从左上角的下拉列表中选择控件,然后从右上角的下拉列表中选择事件来添加处理程序。事件处理程序代码会自动添加到页面中(参见图 1.6)。

图 1.6 从源代码视图添加事件处理程序。

在“设计”视图中,您可以双击一个控件来为其默认事件添加处理程序。双击控件会将您切换到“源代码”视图并添加事件处理程序。

最后,在“设计”视图中,在设计器表面上选择控件后,您可以通过单击“属性”窗口中的“事件”按钮(闪电图标)并在任何事件名称旁边双击来添加事件处理程序(参见图 1.7)。

图 1.7 从属性窗口添加事件处理程序。

您需要理解所有 ASP.NET 控件事件都在服务器上发生。例如,当您实际单击按钮时,不会引发 Click 事件。只有当包含 Button 控件的页面回发到服务器时,才会引发 Click 事件。

ASP.NET 框架是一个服务器端 Web 应用程序框架。您编写的 .NET Framework 代码在服务器上执行,而不是在 Web 浏览器内执行。从 ASP.NET 的角度来看,在页面回发到服务器并可以在 .NET Framework 上下文中执行之前,什么都不会发生。

请注意,清单 1.6 中的 btnSubmit_Click() 处理程序传递了两个参数。所有 ASP.NET 控件的事件处理程序都具有相同的通用签名。

第一个参数,即名为 sender 的对象参数,表示引发事件的控件。换句话说,它表示您单击的 Button 控件。

您可以在页面中的多个控件连接到同一个事件处理程序,并使用第一个参数来确定引发事件的特定控件。例如,清单 1.7 中的页面包含两个 Button 控件。当您单击任一 Button 控件时,Button 控件显示的文本都会更新(参见图 1.8)。

图 1.8 使用一个事件处理程序处理两个 Button 控件。

清单 1.7   ButtonCounters.aspx

 <%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    protected void Button_Click(object sender, EventArgs e)
    {
        Button btn = (Button)sender;
        btn.Text = (Int32.Parse(btn.Text) + 1).ToString();
    }
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
    <title>Button Counters</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    First Counter:
    <asp:Button
        id=”Button1”
        Text=”0”
        OnClick=”Button_Click”
        Runat=”server” />
 
    <br /><br />
 
    Second Counter:
    <asp:Button
        id=”Button2”
        Text=”0”
        OnClick=”Button_Click”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

传递给 Click 事件处理程序的第二个参数,即名为 eEventArgs 参数,表示与事件相关的任何其他事件信息。单击按钮不关联任何其他事件信息,因此在清单 1.6 或清单 1.7 中,第二个参数并不代表任何有用的内容。

另一方面,当您单击 ImageButton 控件而不是 Button 控件时,会将额外的事件信息传递给事件处理程序。单击 ImageButton 控件时,单击位置的 X 和 Y 坐标将传递给处理程序。

清单 1.8 中的页面包含一个显示图片的 ImageButton 控件。当您单击图片时,单击位置的 X 和 Y 坐标将显示在 Label 控件中(参见图 1.9)。

图 1.9 单击 ImageButton。

清单 1.8   ShowEventArgs.aspx

<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    protected void btnElephant_Click(object sender, ImageClickEventArgs e)
    {
        lblX.Text = e.X.ToString();
        lblY.Text = e.Y.ToString();
    }
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
    <title>Show EventArgs</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    <asp:ImageButton
        id=”btnElephant”
        ImageUrl=”Elephant.jpg”
        Runat=”server” OnClick=”btnElephant_Click” />
 
    <br />
    X Coordinate:
    <asp:Label
        id=”lblX”
        Runat=”server” />
    <br />
    Y Coordinate:
    <asp:Label
        id=”lblY”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

传递给 btnElephant_Click() 方法的第二个参数是 ImageClickEventArgs 参数。只要第二个参数不是默认的 EventArgs 参数,您就知道将附加事件信息传递给处理程序。

理解视图状态 (View State)

HTTP 协议是万维网的基本协议,它是一种无状态协议。每次您从网站请求网页时,从网站的角度来看,您都是一个全新的人。

但是,ASP.NET 框架设法超越了 HTTP 协议的这种限制。例如,如果您为 Label 控件的 Text 属性分配了值,那么 Label 控件会在多个页面请求中保留该值。

考虑清单 1.9 中的页面。该页面包含一个 Button 控件和一个 Label 控件。每次单击 Button 控件时,Label 控件显示的值都会增加 1(参见图 1.10)。Label 控件如何在多次回发到 Web 服务器时保留其值?

图 1.10 在多次回发之间保留状态。

清单 1.9   ShowViewState.aspx

<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    protected void btnAdd_Click(object sender, EventArgs e)
    {
        lblCounter.Text = (Int32.Parse(lblCounter.Text) + 1).ToString();
    }
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
    <title>Show  View State</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    <asp:Button
        id=”btnAdd”
        Text=”Add”
        OnClick=”btnAdd_Click”
        Runat=”server” />
 
    <asp:Label
        id=”lblCounter”
        Text=”0”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

ASP.NET 框架使用一种称为视图状态 (View State) 的技巧。如果您在浏览器中打开清单 1.9 中的页面并选择“查看源代码”,您会注意到页面包含一个名为 __VIEWSTATE 的隐藏表单字段,其形式如下:

<input type=”hidden” name=”__VIEWSTATE” id=”__
  VIEWSTATE” value=”/wEPDwUKLTc2ODE1OTYxNw9kFgICBA9kFgIC
  Aw8PFgIeBFRleHQFATFkZGT3tMnThg9KZpGak55p367vfInj1w==” />

此隐藏表单字段包含 Label 控件的 Text 属性的值(以及存储在视图状态中的任何其他控件属性的值)。当页面回发到服务器时,ASP.NET 框架会解析此字符串并重新创建存储在视图状态中的所有属性的值。这样,ASP.NET 框架就可以在多次回发到 Web 服务器时保留控件属性的状态。

默认情况下,视图状态对 ASP.NET 框架中的每个控件都启用。如果您更改 Calendar 控件的背景颜色,新的背景颜色会在多次回发后被记住。如果您更改 DropDownList 中的所选项,所选项会在多次回发后被记住。这些属性的值会自动存储在视图状态中。

视图状态是件好事,但有时它也可能“好过头”。__VIEWSTATE 隐藏表单字段可能会变得很大。将过多数据放入视图状态会减慢页面呈现速度,因为隐藏字段的内容必须在 Web 服务器和 Web 浏览器之间来回传输。

您可以通过启用页面的跟踪来确定页面中每个控件使用的视图状态量(参见图 1.11)。清单 1.10 中的页面在其 <%@ Page %> 指令中包含 Trace=”true” 属性,这会启用跟踪。

图 1.11 查看每个控件的视图状态大小。

清单 1.10   ShowTrace.aspx

<%@ Page Language=”C#” Trace=”true” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    void Page_Load()
    {
        Label1.Text = “Hello World!”;
        Calendar1.TodaysDate = DateTime.Now;
    }
 
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
    <title>Show Trace</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    <asp:Label
        id=”Label1”
        Runat=”server” />
    <asp:Calendar
        id=”Calendar1”
        TodayDayStyle-BackColor=”Yellow”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

当您打开清单 1.10 中的页面时,有关该页面的其他信息会附加到页面底部。控件树部分显示了页面中包含的每个 ASP.NET 控件使用的视图状态量。

每个 ASP.NET 控件都包含一个名为 EnableViewState 的属性。如果将此属性设置为 False,则视图状态对该控件禁用。在这种情况下,控件属性的值不会在多次回发到服务器时被记住。

例如,清单 1.11 中的页面包含两个 Label 控件和一个 Button 控件。第一个 Label 的视图状态被禁用,而第二个 Label 的视图状态被启用。当您单击按钮时,只有第二个 Label 控件的值会从 1 开始递增。

清单 1.11   DisableViewState.aspx

<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    protected void btnAdd_Click(object sender, EventArgs e)
    {
        Label1.Text = (Int32.Parse(Label1.Text) + 1).ToString();
        Label2.Text = (Int32.Parse(Label2.Text) + 1).ToString();
    }
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
    <title>Disable  View State</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    Label 1:
    <asp:Label
        id=”Label1”
        EnableViewState=”false”
        Text=”0”
        Runat=”server” />
 
    <br />
 
    Label 2:
    <asp:Label
        id=”Label2”
        Text=”0”
        Runat=”server” />
 
    <br /><br />
 
    <asp:Button
        id=”btnAdd”
        Text=”Add”
        OnClick=”btnAdd_Click”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

有时,即使您不关心 __VIEWSTATE 隐藏表单字段的大小,也可能希望禁用视图状态。例如,如果您使用 Label 控件显示表单验证错误消息,您可能希望每次提交页面时都从头开始。在这种情况下,只需为 Label 控件禁用视图状态即可。

注意 - ASP.NET Framework 2.0 版本引入了一个名为“控件状态 (Control State)”的新功能,它类似于视图状态,但仅用于保留关键状态信息。例如,GridView 控件使用控件状态来存储选定的行。即使禁用视图状态,GridView 控件也会记住选择了哪一行。

理解 ASP.NET 页面

本节将更详细地探讨 ASP.NET 页面。您将了解动态编译和代码隐藏文件。我们还将讨论 Page 类支持的事件。

理解动态编译

奇怪的是,当您创建 ASP.NET 页面时,您实际上是在创建 .NET 类的源代码。您正在创建一个 System.Web.UI.Page 类的实例。ASP.NET 页面的全部内容,包括所有脚本和 HTML 内容,都会被编译成一个 .NET 类。

当您请求 ASP.NET 页面时,ASP.NET Framework 会查找与该页面对应的 .NET 类。如果不存在相应的类,框架会自动将该页面编译成一个新类,并将编译后的类(程序集)存储在位于以下路径的“临时 ASP.NET 文件”文件夹中:

\WINDOWS\Microsoft.NET\Framework\v4.0.30128\Temporary ASP.NET Files

下次有人请求同一页面时,该页面将不再被编译。将执行先前编译的类,并将结果返回到浏览器。

即使您拔掉 Web 服务器的电源,去婆罗洲待 3 年,然后重新启动 Web 服务器,下次有人请求同一页面时,该页面也不需要重新编译。编译后的类会保存在“临时 ASP.NET 文件”文件夹中,直到修改了应用程序的源代码为止。

当类添加到“临时 ASP.NET 文件”文件夹时,会在类和原始 ASP.NET 页面之间创建文件依赖关系。如果以任何方式修改了 ASP.NET 页面,则相应的 .NET 类会自动删除。下次有人请求该页面时,框架会自动将修改后的页面源编译成一个新的 .NET 类。

这个过程称为动态编译,它使 ASP.NET 应用程序能够支持数千个并发用户。例如,与 ASP Classic 页面不同,ASP.NET 页面不必在每次请求时都进行解析和编译。ASP.NET 页面仅在应用程序修改时进行编译。

注意 - 您可以使用 aspnet_compiler.exe 命令行工具预编译整个 ASP.NET 应用程序。如果您预编译了应用程序,用户就不会遇到第一次页面请求引起的编译延迟。

注意 - 您可以使用 CompilationMode 属性禁用单个页面、文件夹中的页面或整个网站的动态编译。当 CompilationMode 属性与 <%@ Page %> 指令一起使用时,它允许您禁用单个页面的动态编译。当 compilationMode 属性与 Web.config 文件中的 pages 元素一起使用时,它允许您禁用文件夹或整个应用程序的动态编译。

禁用编译对于您网站中有数千个页面并且不想将过多程序集加载到内存中的情况很有用。当 CompilationMode 属性设置为 Never 时,页面永远不会被编译,也不会为页面生成程序集。页面在运行时进行解释。

您不能为包含服务器端代码的页面禁用编译。特别是,no compile 页面不能包含服务器端 <script>...</script> 块。另一方面,no compile 页面可以包含 ASP.NET 控件和数据绑定表达式。

如果您好奇,我已包含清单 1.12 中与 FirstPage.aspx 页面对应的类的源代码。(为了节省空间,我已清理代码并使其更简洁。)我在启用应用程序调试后从“临时 ASP.NET 文件”文件夹中复制了此文件。

清单 1.12   FirstPage.aspx 源代码

namespace ASP
{
    using System.Web.Security;
    using System.Web;
    using System.Web.SessionState;
    using System.Text;
    using System.Collections.Specialized;
    using System.Web.Profile;
    using System.Net.Mail;
    using System.Collections;
    using System.Web.UI.WebControls.WebParts;
    using System.Configuration;
    using System;
    using System.Web.Caching;
    using System.Web.UI;
    using System.Text.RegularExpressions;
    using System.Web.UI.WebControls;
    using System.Web.UI.HtmlControls;
 
    [System.Runtime.CompilerServices.CompilerGlobalScopeAttribute()]
    public class firstpage_aspx : global::System.Web.UI.Page,
System.Web.SessionState.IRequiresSessionState, System.Web.IHttpHandler
    {
        protected global::System.Web.UI.WebControls.Label lblServerTime;
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
        private static bool @__initialized;
        private static object @__fileDependencies;
 
        void Page_Load()
        {
            lblServerTime.Text = DateTime.Now.ToString();
        }
 
        public firstpage_aspx()
        {
            string[] dependencies;
            ((global::System.Web.UI.Page)(this)).AppRelativeVirtualPath =
            “~/FirstPage.aspx”;
            if ((global::ASP.firstpage_aspx.@__initialized == false))
            {
                dependencies = new string[1];
                dependencies[0] = “~/FirstPage.aspx”;
                global::ASP.firstpage_aspx.@__fileDependencies =
this.GetWrappedFileDependencies(dependencies);
                global::ASP.firstpage_aspx.@__initialized = true;
            }
            this.Server.ScriptTimeout = 30000000;
        }
 
        protected System.Web.Profile.DefaultProfile Profile
        {
            get
            {
                return ((System.Web.Profile.DefaultProfile)(this.Context.Profile));
            }
        }
 
        protected System.Web.HttpApplication ApplicationInstance
        {
            get
            {
                return ((System.Web.HttpApplication)(this.Context.Application Instance));
            }
        }
 
        private global::System.Web.UI.WebControls.Label @__BuildControllbl ServerTime()
        {
        ...code...
        }
 
        private global::System.Web.UI.HtmlControls.HtmlForm @__BuildControlform1()
        {
        ...code...
        }
 
        private void @__BuildControlTree(firstpage_aspx @__ctrl)
        {
        ...code...
        }
 
        protected override void FrameworkInitialize()
        {
            base.FrameworkInitialize();
            this.@__BuildControlTree(this);
            this.AddWrappedFileDependencies(global::ASP.firstpage_aspx.@__file Dependencies);
            this.Request.ValidateInput();
        }
 
        public override int GetTypeHashCode()
        {
            return 243955639;
        }
 
        public override void ProcessRequest(System.Web.HttpContext context)
        {
            base.ProcessRequest(context);
        }
    }
}

清单 1.12 中的类继承自 System.Web.UI.Page 类。当 ASP.NET Framework 显示页面时,会调用 ProcessRequest() 方法。此方法构建页面的控件树,这是下一节的主题。

理解控件树

在上节中,您了解到 ASP.NET 页面实际上是一个 .NET 类的源代码。或者,您可以将 ASP.NET 页面视为一组控件。更准确地说,由于某些控件可能包含子控件,因此您可以将 ASP.NET 页面视为一个控件树。

例如,清单 1.13 中的页面包含一个 DropDownList 控件和一个 Button 控件。此外,由于 <%@ Page %> 指令具有 Trace=”true” 属性,因此为页面启用了跟踪。

清单 1.13   ShowControlTree.aspx

<%@ Page Language=”C#” Trace=”true” %>
<!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>Show Control Tree</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    <asp:DropDownList
        id=”DropDownList1”
        Runat=”server”>
        <asp:ListItem Text=”Oranges” />
        <asp:ListItem Text=”Apples” />
    </asp:DropDownList>
 
    <asp:Button
        id=”Button1”
        Text=”Submit”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

当您在浏览器中打开清单 1.13 中的页面时,您可以在页面底部看到该页面的控件树。其形式如下:

__Page ASP.showcontroltree_aspx
    ctl02 System.Web.UI.LiteralControl
    ctl00 System.Web.UI.HtmlControls.HtmlHead
        ctl01 System.Web.UI.HtmlControls.HtmlTitle
    ctl03 System.Web.UI.LiteralControl
    form1 System.Web.UI.HtmlControls.HtmlForm
        ctl04 System.Web.UI.LiteralControl
        DropDownList1 System.Web.UI.WebControls.DropDownList
        ctl05 System.Web.UI.LiteralControl
        Button1 System.Web.UI.WebControls.Button
        ctl06 System.Web.UI.LiteralControl
    ctl07

控件树中的根节点是页面本身。页面 ID 为 __Page。页面类在其子控件集合中包含所有其他控件。控件树还包含一个名为 form1 的 HtmlForm 类实例。此控件是页面中包含的服务器端表单标签。它包含所有其他表单控件——DropDownList 和 Button 控件——作为子控件。

控件树中夹杂着几个 LiteralControl 控件。这些控件是什么?

请记住,ASP.NET 页面中的所有内容都会转换为 .NET 类,包括页面中的任何 HTML 或纯文本内容。LiteralControl 类代表页面中的 HTML 内容(包括标签之间的任何回车符)。

注意 - 通常,您通过 ID 引用页面中的控件。但是,在某些情况下不可能这样做。在这些情况下,您可以使用 Control 类的 FindControl() 方法来检索具有特定 ID 的控件。FindControl() 方法类似于 JavaScript 的 getElementById() 方法。

使用代码隐藏页

ASP.NET Framework(和 Visual Web Developer)允许您创建两种不同类型的 ASP.NET 页面:单文件和双文件 ASP.NET 页面。

本书中的所有代码示例都以单文件 ASP.NET 页面的形式编写。在单文件 ASP.NET 页面中,单个文件同时包含页面代码和页面控件。页面代码包含在 <script runat=”server”> 标签中。

作为单文件 ASP.NET 页面的替代方案,您可以创建双文件 ASP.NET 页面。双文件 ASP.NET 页面通常称为代码隐藏页面。在代码隐藏页面中,页面代码包含在单独的文件中。

注意 - ASP.NET 2.0 Framework 后的代码隐藏页工作方式与 ASP.NET 1.x Framework 不同。在 ASP.NET 1.x 中,代码隐藏页的两个部分通过继承相关联。在 ASP.NET 2.0 Framework 之后,代码隐藏页的两个部分通过部分类和继承的组合相关联。

例如,清单 1.14 和清单 1.15 包含代码隐藏页的两个部分。

Visual Web Developer 注意 - 使用 Visual Web Developer 时,通过选择“网站”->“添加新项”并选择“Web 窗体”项,然后在添加页面之前选中“将代码放在单独的文件中”复选框来创建代码隐藏页面。

清单 1.14   FirstPageCodeBehind.aspx

<%@ Page Language=”C#” AutoEventWireup=”true” 
    CodeFile=”FirstPageCodeBehind.aspx.cs ”Inherits=”FirstPageCodeBehind” %>
<!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>First Page Code-Behind</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    <asp:Button
        id=”Button1”
        Text=”Click Here”
        OnClick=”Button1_Click”
        Runat=”server” />
 
    <br /><br />
 
    <asp:Label
        id=”Label1”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

清单 1.15   FirstPageCodeBehind.aspx.cs

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
 
public partial class FirstPageCodeBehind : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Label1.Text = “Click the Button”;
    }
 
    protected void Button1_Click(object sender, EventArgs e)
    {
        Label1.Text = “Thanks!”;
    }
}

清单 1.14 中的页面称为表示页。它包含一个 Button 控件和一个 Label 控件。但是,该页面不包含任何代码。所有代码都包含在代码隐藏文件中。

Visual Web Developer 注意 - 您可以通过右键单击页面并选择“查看代码”来切换到页面的代码隐藏文件。

清单 1.15 中的代码隐藏文件包含 Page_Load()Button1_Click() 处理程序。清单 1.15 中的代码隐藏文件不包含任何控件。

请注意,清单 1.14 中的页面在其 <%@ Page %> 指令中同时包含 CodeFileInherits 属性。这些属性将页面与其代码隐藏文件链接。

代码隐藏的工作原理:细节

在 ASP.NET Framework 的早期版本(ASP.NET 1.x)中,代码隐藏页面会生成两个类。一个类对应于表示页,一个类对应于代码隐藏文件。这些类通过类继承相关联。表示页类继承自代码隐藏文件类。

这种将表示页与它们的代码隐藏文件关联的方法存在一个问题,那就是它非常脆弱。继承是单向关系。母亲的所有属性都适用于女儿,反之则不然。在表示页中声明的任何控件都必须在代码隐藏文件中声明。此外,控件必须以完全相同的 ID 声明。否则,继承关系将中断,并且由控件引发的事件将无法在代码隐藏文件中处理。

在 ASP.NET 2.0 的 Beta 版本中,采用了一种完全不同的方法来将表示页与其代码隐藏文件关联。这种新方法要脆弱得多。代码隐藏页的两个部分不再通过继承相关联,而是通过 .NET 2.0 Framework 支持的一项新技术——部分类

注意 - 第 17 章“构建组件”将讨论部分类。

部分类允许您在多个物理文件中声明一个类。编译类时,将从所有部分类生成一个类。一个部分类的任何成员——包括任何私有字段、方法和属性——都可以被同一类的任何其他部分类访问。这是有道理的,因为部分类最终会被合并以创建一个最终类。

使用部分类的优点是您不必担心在表示页和代码隐藏文件中同时声明控件。在表示页中声明的任何内容都可以在代码隐藏文件中自动访问,而在代码隐藏文件中声明的任何内容都可以在表示页中自动访问。

ASP.NET 2.0 Framework 的 Beta 版本使用部分类来关联表示页与其代码隐藏文件。但是,ASP.NET 1.x Framework 的某些高级功能与使用部分类不兼容。为了支持这些高级功能,在 ASP.NET 2.0 Framework 的最终版本中采用了更复杂的方法来将表示页与代码隐藏文件关联。这种方法在 ASP.NET 4 中仍然是标准。

自 ASP.NET 2.0 版本以来,该框架结合使用了继承和部分类来关联表示页和代码隐藏文件。创建代码隐藏页面时,会生成三个类。

前两个类对应于表示页。例如,当您创建 FirstPageCodeBehind.aspx 页面时,在“临时 ASP.NET 文件”文件夹中会自动生成以下两个类:

public partial class FirstPageCodeBehind
{
    protected System.Web.UI.WebControls.Button Button1;
    protected System.Web.UI.WebControls.Label Label1;
 
    ... additional code ...
}
 
public class firstpagecodebehind_aspx : FirstPageCodeBehind
{
    ... additional code ...
}

第三个类对应于代码隐藏文件。对应于 FirstPageCodeBehind.aspx.cs 文件,会生成以下类:

public partial class FirstPageCodeBehind : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Label1.Text = “Click the Button”;
    }
 
    protected void Button1_Click(object sender, EventArgs e)
    {
        Label1.Text = “Thanks!”;
    }
}

当从浏览器请求 FirstPageCodeBehind.aspx 页面时,会执行 firstpagecodebehind_aspx 类。该类继承自 FirstPageCodeBehind 类。FirstPageCodeBehind 类是一个部分类。它会被生成两次:一次由表示页生成,一次由代码隐藏文件生成。

ASP.NET 框架结合使用部分类和继承来关联表示页和代码隐藏文件。由于页面类和代码隐藏类是部分类,与以前的 ASP.NET 版本不同,您不再需要在表示页和代码隐藏页中同时声明控件。在表示页中声明的任何控件都可以自动在代码隐藏文件中访问。由于页面类继承自代码隐藏类,ASP.NET Framework 继续支持 ASP.NET 1.x Framework 的高级功能,例如自定义基类 Page 类。

在单文件和代码隐藏页面之间做出选择

那么,何时应该使用单文件 ASP.NET 页面,何时应该使用代码隐藏页面?这是一个偏好选择。关于这个话题,互联网上的博客中存在激烈的争论。

我听到的说法是,代码隐藏页面优于单文件页面,因为代码隐藏页面可以更清晰地分离用户界面和应用程序逻辑。这种论证的问题在于,分离用户界面与应用程序逻辑的正常理由是代码重用。创建代码隐藏页面并不能促进代码重用。重用多个页面的应用程序逻辑的更好方法是构建单独的组件库。(本书第四部分将探讨此主题。)

最终,这是一个个人偏好问题。我们所参与的大多数企业项目都使用代码隐藏页面,但在适当的情况下,单文件页面也是完全可以接受的。

处理页面事件

每次请求 ASP.NET 页面时,都会按特定顺序引发一组特定的事件。这个事件序列称为页面执行生命周期

例如,我们在以前的代码示例中已经使用了 Page Load 事件。您通常使用 Page Load 事件来初始化页面中控件的属性。但是,Page Load 事件只是 Page 类支持的事件之一。

以下是每次请求页面时引发的事件顺序:

  1. PreInit
  2. Init
  3. InitComplete
  4. PreLoad
  5. Load (加载)
  6. LoadComplete
  7. PreRender
  8. PreRenderComplete
  9. SaveStateComplete
  10. Unload

为什么会有这么多事件?在页面执行生命周期的不同阶段,会发生不同的事情,并且可用的信息也不同。

例如,视图状态在 InitComplete 事件之后才加载。从表单控件(如 TextBox 控件)发布到服务器的数据,在此时钟事件之后才可用。

百分之九十九的情况下,您不会处理除 LoadPreRender 事件以外的任何事件。这两个事件之间的区别在于,Load 事件发生在任何控件事件之前,而 PreRender 事件发生在任何控件事件之后。

清单 1.16 中的页面说明了 LoadPreRender 事件之间的区别。该页面包含三个事件处理程序:一个用于 Load 事件,一个用于 Button Click 事件,一个用于 PreRender 事件。每个处理程序都会向 Label 控件添加一条消息(图 1.12)。

图 1.12 查看页面事件顺序。

清单 1.16   ShowPageEvents.aspx

<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    void Page_Load(object sender, EventArgs e)
    {
        Label1.Text = “Page Load”;
    }
 
    void Button1_Click(object sender, EventArgs e)
    {
        Label1.Text += “<br />Button Click”;
    }
 
    void Page_PreRender()
    {
        Label1.Text += “<br />Page PreRender”;
    }
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
    <title>Show Page Events</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    <asp:Button
        id=”Button1”
        Text=”Click Here”
        OnClick=”Button1_Click”
        Runat=”server” />
 
    <br /><br />
 
    <asp:Label
        id=”Label1”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

当您单击 Button 控件时,Click 事件在 Load 事件之后、PreRender 事件之前在服务器上发生。

您还应该注意到清单 1.16 中页面的另一个方面,即事件处理程序如何连接到 Page 事件。ASP.NET 页面支持一个名为 AutoEventWireUp 的功能,该功能默认启用。如果您将一个子例程命名为 Page_Load(),则该子例程会自动处理 Page Load 事件;如果您将一个子例程命名为 Page_PreRender(),则该子例程会自动处理 Page PreRender 事件,依此类推。

警告 - AutoEventWireUp 对所有页面事件都有效。例如,它对 Page_InitComplete() 事件无效。

使用 Page.IsPostBack 属性

Page 类包含一个名为 IsPostBack 属性的属性,您可以使用它来检测页面是否已回发到服务器。

由于视图状态,当您初始化控件属性时,您不想在每次页面加载时都初始化该属性。因为视图状态会在页面回发之间保存控件属性的状态,所以您通常只在页面首次加载时初始化控件属性一次。

如果重新初始化控件属性,许多控件将无法正常工作。在这些情况下,您必须使用 IsPostBack 属性来检测页面是否已回发。

清单 1.17 中的页面说明了如何在向 DropDownList 控件添加项目时使用 Page.IsPostBack 属性。

清单 1.17   ShowIsPostBack.aspx

<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    void Page_Load()
    {
        if (!Page.IsPostBack)
        {
            // Create collection of items
            ArrayList items = new ArrayList();
            items.Add(“Apples”);
            items.Add(“Oranges”);
 
            // Bind to DropDownList
            DropDownList1.DataSource = items;
            DropDownList1.DataBind();
        }
    }
 
    protected void Button1_Click(object sender, EventArgs e)
    {
        Label1.Text = DropDownList1.SelectedItem.Text;
    }
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
    <title>Show IsPostBack</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    <asp:DropDownList
        id=”DropDownList1”
        Runat=”server” />
 
    <asp:Button
        id=”Button1”
        Text=”Select”
        OnClick=”Button1_Click”
        Runat=”server” />
 
    <br /><br />
 
    You selected:
    <asp:Label
        id=”Label1”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

在清单 1.17 中,Page_Load() 事件处理程序中的代码仅在页面首次加载时执行一次。当您再次回发页面时,IsPostBack 属性返回 True,并且 Page_Load() 处理程序中的代码将被跳过。

如果从 Page_Load() 方法中删除 IsPostBack 检查,您将得到一个奇怪的结果。DropDownList 始终将其第一个项目显示为选定项目。将 DropDownList 绑定到项目集合会重新初始化 DropDownList 控件。因此,您只想在页面首次加载时绑定 DropDownList 控件一次。

调试和跟踪 ASP.NET 页面

现实生活中令人沮丧的事实是,您花费的大部分开发时间都用于调试应用程序。在本节中,您将学习如何在开发 ASP.NET 页面时查看详细的错误消息。您还将学习如何显示自定义跟踪消息,以便在调试页面时使用。

调试 ASP.NET 页面

如果您需要在执行页面时查看详细的错误消息,则需要为页面或整个应用程序启用调试。通过在 <%@ Page %> 指令中添加 Debug=”true” 属性,可以为页面启用调试。例如,清单 1.18 中的页面已启用调试。

清单 1.18   ShowError.aspx

 <%@ Page Language=”C#” Debug=”true” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    void Page_Load()
    {
        int zero = 0;
        Label1.Text = (1 / zero).ToString();
    }
 
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
    <title>Show Error</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    <asp:Label
        id=”Label1”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

当您在 Web 浏览器中打开清单 1.18 中的页面时,会显示详细的错误消息(参见图 1.13)。

图 1.13 查看详细错误消息。

警告 - 在将应用程序投入生产之前,请务必禁用调试。当应用程序在调试模式下编译时,编译器无法进行某些性能优化。

您可以将调试添加到 Web.config 文件中,而不是为单个页面启用调试。将清单 1.19 中的 Web.config 文件添加到您的应用程序中,可以为整个应用程序启用调试。

清单 1.19   Web.Config

<?xml version=”1.0”?>
<configuration>
<system.web>
  <compilation debug=”true” />
</system.web>
</configuration>

在调试位于远程 Web 服务器上的 ASP.NET 应用程序时,需要禁用自定义错误。出于安全原因,默认情况下,当您从远程计算机请求页面时,ASP.NET Framework 不会显示错误消息。启用自定义错误后,您在远程计算机上看不到错误。清单 1.20 中修改后的 Web.config 文件禁用自定义错误。

清单 1.20   Web.Config

<?xml version=”1.0”?>
<configuration>
<system.web>
  <compilation debug=”true” />
  <customErrors mode=”Off” />
</system.web>
</configuration>

警告 - 出于安全和性能原因,请勿在调试已启用、自定义错误已禁用或跟踪已启用的情况下将网站投入生产。在生产服务器上,在 machine.config 文件的 system.web 部分中添加以下元素:

<deployment retail=”true”/>

添加此元素将禁用调试模式,启用远程自定义错误,并禁用跟踪。您应该将此元素添加到位于所有生产服务器上的 machine.config 文件中。

使用 Visual Web Developer 调试页面

如果您使用 Visual Web Developer,可以通过对页面或整个网站执行生成来显示编译错误消息。选择“生成”->“生成页面”或“生成”->“生成网站”。编译错误消息和警告列表将出现在“错误列表”窗口中(参见图 1.14)。您可以双击任何错误直接导航到导致错误的rible代码。

图 1.14 在 Visual Web Developer 中执行生成。

如果您需要进行更高级的调试,可以使用 Visual Web Developer 的调试器。调试器允许您设置断点并逐行执行代码。通过在源代码视图的最左侧列双击来设置断点。添加断点时,会出现一个红点(参见图 1.15)。

图 1.15 设置断点。

设置断点后,通过选择“调试”->“开始调试”来运行应用程序。当命中断点时,执行将停止。此时,您可以将鼠标悬停在任何变量或控件属性上以查看变量或控件属性的当前值。

注意 - 您可以将应用程序中的一个页面指定为启动页。这样,无论您打开哪个页面,每次运行应用程序时都会执行启动页。通过在“解决方案资源管理器”窗口中右键单击页面并选择“设为启动页”来设置启动页。

命中断点后,您可以选择“调试”菜单或工具栏中的“逐行调试”、“跳过”或“跳出”来继续执行。以下是每个选项的说明:

  • 逐行调试 — 执行下一行代码
  • 跳过 — 不离开当前方法即可执行下一行代码
  • 跳出 — 执行下一行代码并返回调用当前方法的函数

调试完页面后,您可以从“调试”菜单或工具栏中选择一个特定选项来继续、停止或重新启动应用程序。

跟踪页面执行

如果您想在页面执行时输出跟踪消息,可以为特定页面或整个应用程序启用跟踪。ASP.NET Framework 支持页面级跟踪和应用程序级跟踪。

清单 1.21 中的页面说明了如何利用页面级跟踪。

清单 1.21   PageTrace.aspx

 <%@ Page Language=”C#” Trace=”true” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
 
    void Page_Load()
    {
        for (int counter = 0; counter < 10; counter++)
        {
           ListBox1.Items.Add(“item “ + counter.ToString());
            Trace.Warn(“counter=” + counter.ToString());
        }
    }
 
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
    <title>Page Trace</title>
</head>
<body>
    <form id=”form1” runat=”server”>
    <div>
 
    <asp:ListBox
        id=”ListBox1”
        Runat=”server” />
 
    </div>
    </form>
</body>
</html>

清单 1.21 中的 <%@ Page %> 指令包含 trace=”true” 属性。此属性启用跟踪,并在页面底部附加一个“跟踪信息”部分(参见图 1.16)。

图 1.16 查看页面跟踪信息。

此外,请注意 Page_Load() 处理程序使用 Trace.Warn() 方法将消息写入“跟踪信息”部分。您可以将任何字符串输出到“跟踪信息”部分。在清单 1.21 中,显示了变量 counter 的当前值。

当您想确定页面执行过程中具体发生了什么时,需要利用页面跟踪。您可以在代码中的任何需要的地方调用 Trace.Warn() 方法。因为即使页面出现错误,“跟踪信息”部分也会显示,所以您可以使用跟踪来诊断任何页面错误的根源。

页面跟踪的一个缺点是,全世界的每个人都能看到您的跟踪信息。您可以通过利用应用程序级跟踪来解决此问题。启用应用程序级跟踪时,跟踪信息仅在请求一个名为 Trace.axd 的特殊页面时显示。

要启用应用程序级跟踪,您需要将清单 1.22 中的 Web.config 文件添加到您的应用程序中。

清单 1.22   Web.Config

<?xml version=”1.0”?>
<configuration>
<system.web>
    <trace enabled=”true” />
</system.web>
</configuration>

将清单 1.22 中的 Web.config 文件添加到您的应用程序后,您可以在浏览器中请求 Trace.axd 页面。启用应用程序级跟踪后,最后 10 次页面请求将显示出来。

警告 - 默认情况下,不能从远程计算机请求 Trace.axd 页面。如果您需要远程访问 Trace.axd 页面,则需要在 Web.config 文件中的 trace 元素中添加 localOnly=”false” 属性。

如果您单击列出的任何页面请求旁边的“查看详细信息”链接,您将能够查看页面输出的所有跟踪消息。即使禁用页面级跟踪,使用 Trace.Warn() 方法写入的消息也会由 Trace.axd 页面显示。

注意 - 您可以使用 trace 元素的 writeToDiagnosticsTrace 新属性将所有跟踪消息写入 Visual Web Developer 的输出窗口,当您运行应用程序时。您可以使用 mostRecent 新属性来显示最后 10 次页面请求,而不是显示启用跟踪后的 10 次页面请求。

警告 - 如果在启用应用程序级跟踪时未启用 mostRecent 属性,则跟踪将在 10 页后停止。

安装 ASP.NET

安装 ASP.NET Framework 最简单的方法是安装 Visual Web Developer Express。您可以从 http://www.ASP.net(Microsoft ASP.NET 官方网站)下载最新版本的 Visual Web Developer。

安装 Visual Web Developer Express 还会安装以下组件:

  • Microsoft .NET Framework 4 版本
  • SQL Server Express

Visual Web Developer Express 与以下操作系统兼容:

  • Windows XP (x86) Service Pack 3
  • Windows XP (x64) Service Pack 2
  • Windows Server 2003 Service Pack 2
  • Windows Server 2003 R2
  • Windows Server 2008 Service Pack 2
  • Windows Server 2008 R2
  • Windows Vista
  • Windows 7

您可以在已安装了其他版本 Visual Studio 或 Visual Web Developer 的计算机上安装 Visual Web Developer Express。不同版本的开发环境可以和谐共存。

此外,同一个 Web 服务器可以服务 ASP.NET 1.1 页面、ASP.NET 2.0 页面、ASP.NET 3.0 页面、ASP.NET 3.5 页面和 ASP.NET 4 页面。每个版本的 .NET Framework 都安装在以下文件夹中:

C:\WINDOWS\Microsoft.NET\Framework

例如,在我的计算机上,安装了以下六个版本的 .NET Framework(版本 1.0、版本 1.1、版本 2.0、版本 3.0、版本 3.5 和版本 4):

C:\WINDOWS\Microsoft.NET\Framework\v1.0.3705
C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
C:\WINDOWS\Microsoft.NET\Framework\v3.0
C:\WINDOWS\Microsoft.NET\Framework\v3.5
C:\WINDOWS\Microsoft.NET\Framework\v4.0.30128

注意 - Framework 目录包含 32 位 (x86) 版本的 .NET。如果您运行的是 64 位 (x64) 操作系统,那么还有一个名为 Framework64 的目录。

除 v3.0 和 v3.5 之外的所有文件夹都包含一个名为 aspnet_regiis.exe 的命令行工具。您可以使用此工具将特定虚拟目录与特定版本的 .NET Framework 关联起来。

例如,从位于 v1.0.3705、v1.1.4322、v2.0.50727 或 v4.0.30128 文件夹的命令提示符执行以下命令,可以为名为 MyApplication 的虚拟目录启用 1.0、1.1、2.0 或 4 版本的 ASP.NET:

aspnet_regiis -s W3SVC/1/ROOT/MyApplication

通过执行位于不同 .NET Framework 版本文件夹中的 aspnet_regiis.exe 工具,您可以将特定虚拟目录映射到任何版本的 ASP.NET Framework。

.NET Frameworks 3.0 和 3.5 的工作方式与早期版本不同。3.0 和 3.5 版本建立在现有的 .NET Framework 2.0 之上。要使用这些版本的 .NET Framework,您需要在网站中添加正确的程序集引用,并使用正确版本的 C# 或 VB.NET 编译器。您在应用程序的 web.config 文件中引用这些程序集并配置编译器。当您在 Visual Web Developer 中创建新网站时,必要的配置设置会自动包含在您的 web.config 文件中。.NET Framework 4 是自 2.0 版本以来第一个不基于先前版本的版本。

您还可以选择定位特定版本的 .NET Framework。为此,请选择“网站”->“启动选项”,然后选择“生成”选项卡。您可以选择定位 .NET Framework 2.0、.NET Framework 3.0、.NET Framework 3.5 或 .NET Framework 4(参见图 1.17)。

图 1.17 定位特定版本的 .NET Framework。

注意 - 如果您将现有的 ASP.NET 2.0、3.0 或 3.5 网站加载到 Visual Web Developer 2010 中,Visual Web Developer 会提示您将网站升级到 ASP.NET 4。当 Visual Web Developer 升级您的网站时,它会修改您的 web.config 文件。

摘要

在本章中,您了解了 ASP.NET 4 Framework。首先,我们构建了一个简单的 ASP.NET 页面。您了解了 ASP.NET 页面的三个主要元素:指令、代码声明块和页面呈现块。

接下来,我们讨论了 .NET Framework。您了解了 Framework 类库中包含的 13,000 个类以及通用语言运行时的功能。

您还获得了 ASP.NET 控件的概述。您了解了 .NET Framework 中包含的不同控件组。您还学习了如何处理控件事件以及如何利用视图状态。

我们还讨论了 ASP.NET 页面。您了解了 ASP.NET 页面在首次请求时如何被动态编译。我们还研究了如何将单文件 ASP.NET 页面分成一个代码隐藏页面。您学习了如何调试和跟踪 ASP.NET 页面的执行。

在本章的结尾,我们涵盖了有关 ASP.NET Framework 的安装问题。您学习了如何将不同的虚拟目录映射到不同版本的 ASP.NET Framework。您还学习了如何在 Web.config 文件中定位不同版本的 .NET Framework。


© Pearson Education 版权所有。保留所有权利。

© . All rights reserved.