ASP.NET 状态管理
应用程序状态包括任何影响应用程序行为的信息或数据:目录、购物车、用户选项、检查列表和命中计数器都是应用程序状态的示例。对于要生成基于状态的解决方案的应用程序开发人员来说,可采用的用法模式、数据类型和访问方法多种多样,所以状态管理会比较复杂。
ASP.NET 提供便于使用的应用程序状态和会话状态管理功能,这些功能是 ASP 开发人员所熟悉的,可以容易地与所有其他 .NET Framework API 兼容。
本节内容
- 应用程序状态
- 介绍应用程序状态。
- 会话状态
- 介绍会话状态。
- 在页间传递服务器控件值
- 介绍如何在 Web 窗体页之间传递信息。
相关章节
- 创建 ASP.NET Web 应用程序
- 提供 ASP.NET 应用程序的概述。
您可以使用 HttpApplicationState 类在整个应用程序中共享信息,该类通常是通过 HttpContext 对象的 Application 属性进行访问的。该类公开对象的键/值字典,您可以使用该字典来存储 .NET Framework 对象和与来自多个客户端的多个 Web 请求相关的标量值。
本主题提供应用程序状态的概述;讨论如何使用应用程序状态;介绍应用程序状态集合;并说明应用程序状态同步。
应用程序状态概述
ASP.NET 应用程序是具有以下特征的所有文件、页、处理程序、模块和代码的总和:它们都驻留在给定虚拟目录及其子目录中;它们都可以由用户通过虚拟目录层次结构来请求。
例如,如果开发一个计算您公司 Intranet 投资收益的应用程序,则可能将其发布在 Web 服务器上名为 \Invest 的虚拟目录中。这样一个应用程序的目录结构可能看上去像:
\Invest
\bin
\image
\xml
任何客户端首次在特定 ASP.NET 应用程序的虚拟目录命名空间中请求 URL 资源时,都将创建 HttpApplicationState 类的实例。存储在计算机上的每个 Web 应用程序都是这种情况。对这种按应用程序的实例的访问权限是通过名为 Application 的 HttpContext 属性提供的。所有 HttpModule 和 HttpHandler(例如 ASP.NET 页)都具有对上下文的实例的访问权限,因此具有在给定 Web 请求过程中对 Application 属性的访问权限。
ASP.NET 提供以下应用程序状态支持:
- 与 ASP 的早期版本兼容的便于使用的状态功能,该功能可以与所有 .NET 支持的语言一起使用,并且与其他 .NET Framework API 一致。
- 应用程序状态字典,在应用程序中调用的所有请求处理程序都可以使用该字典。与 Internet 信息服务 (IIS) 和 ASP 的早期版本中只有页可以访问应用程序状态不同,所有 IHttpHandler 和 IHttpModule 实例都可以存储和检索字典中的全局变量。
- 简单直观的同步机制,它使开发人员可以方便地协调对存储在应用程序状态中的变量的并发访问。
- 只能从在发起应用程序的上下文中运行的代码访问的应用程序状态值。运行在系统上的其他应用程序不能访问或修改这些值。
最常见的方法是通过 Page 对象的 Application 属性来访问应用程序状态。
使用应用程序状态
实际上,应用程序状态变量是给定 ASP.NET 应用程序的全局变量。像客户端应用程序开发人员一样,ASP.NET 程序员应该始终考虑将任何对象存储为全局变量所具有的影响。
关于这一点,下面的几个问题非常重要:
- 在应用程序状态中进行存储的内存影响。存储在应用程序状态中的变量占用的内存在移除或替换该值之前不被释放,这与单独的 Web 页不同,对于 Web 页,所有资源在 Web 请求结束时都将强行释放。例如,若在应用程序状态中永久保留很少使用的 10 MB 记录集,就算不上不是对系统资源的高效使用。对于这个极端的示例,您可能发现使用 ASP.NET Cache 是更好的解决方案。
- 在多线程服务器环境中存储和访问全局变量所涉及的并发和同步问题。应用程序中的多个线程可以同时访问存储在应用程序状态中的值。您应该始终注意确保如果应用程序范围的对象是自由线程的,则它包含内置的同步支持。所有面向公共语言运行库的自定义对象都是自由线程的。如果应用程序范围的对象不是自由线程的,则您必须确保在其周围编写显式同步方法代码以避免死锁、争用状态和访问冲突。
- 在多线程服务器环境中存储和访问全局变量所涉及的可缩放性问题。只要试图写入或更新文件,就应该使用锁。保护全局资源的锁本身就是全局的,运行在访问全局资源的多个线程上的代码最终对这些锁产生竞争。这会导致操作系统在锁可用之前阻塞辅助线程。在高负载的服务器环境中,这种阻塞可能导致系统上严重的线程颠簸。在多处理器系统上,它可能导致处理器不能充分利用(因为在理论上,在等待共享锁时,可以停止某个处理器的所有线程),从而导致整体可缩放性的严重下降。
- 存储在应用程序状态中的信息的生命周期问题。在应用程序执行过程中,承载基于 .NET 的应用程序的 .NET Framework 应用程序域或进程可能会随时被强行停止并销毁,原因可能是出现了故障、进行了代码更新、重新启动了预定的进程等。因为存储在应用程序状态中的数据不是持久的,所以如果包含它的宿主被损坏,数据也将丢失。如果希望状态在这些类型的故障中保存下来,您应该将其存储在数据库或其他可持久的存储区中。
- 应用程序状态不能跨网络场或网络园共享,所谓的网络场是指应用程序由多台服务器承载的情况;网络园是指应用程序被同一台服务器上的多个进程承载的情况。这两种情况任何一个中的应用程序状态中存储的变量只对于应用程序在其中运行的特定进程是全局的。每个应用程序进程都可以具有不同的值。因此,您不能依靠应用程序状态来存储唯一值或更新全局计数器,例如在网络场和网络园情况下。
虽然存在这些问题,但是设计良好的应用程序级别的变量在 Web 应用程序中可以发挥强大的功能。您可以进行信息的一次性(即不经常的)加载和计算,然后使用应用程序状态对其进行缓存,从而在稍后的 Web 请求中可以从内存中快速对其进行访问。
例如,一个股市 Web 站点可能在一天中每 5 分钟从数据库获取大量的金融股票信息(也许是 40 MB 的数据),然后将这些信息缓存在应用程序状态中,这样所有以后的查找请求都可以在应用程序状态中访问这些信息。其结果是极大地提高了每个请求的性能,因为传入的请求不需要跨进程、跨计算机或数据库的往返过程。另一方面,对大的、瞬态的数据块来说,使用资源较好的方法可能是使用缓存。
应用程序状态集合
HttpApplicationState 类公开两个状态集合:Contents 和 StaticObjects。
Contents 集合公开已直接通过代码添加到应用程序状态集合的所有变量项。例如:
[Visual Basic] ' Visual Basic code from within a page, a handler, or Global.asax. Application("Message") = "MyMsg" Application("AppStartTime") = Now [C#] // C# code from within a page, a handler, or Global.asax. Application["Message"] = " MyMsg"; Application["AppStartTime"] = DateTime.Now;
为了与 ASP 的早期版本兼容,还可以使用应用程序对象的 Contents 属性访问这些变量,如下面的示例所示。
[Visual Basic] ' Visual Basic code from within a page, a handler, or Global.asax. Application.Contents("Message") = " MyMsg" Application.Contents("AppStartTime") = Now [C#] // Visual Basic code from within a page, a handler, or Global.asax. Application.Contents["Message"] = " MyMsg"; Application.Contents["AppStartTime"] = DateTime.Now;
StaticObjects 集合公开所有已经通过 Global.asax 文件中带有应用程序范围的 <
object runat="server">
标记添加到应用程序状态集合的变量项。例如:
' Global.asax definition. <object runat="server" scope="application" ID="MyInfo" PROGID="MSWC.MYINFO"> </OBJECT>
对象不能从 ASP.NET 应用程序内的其他任何地方添加到 StaticObjects 集合中。如果您试图直接通过代码添加对象,集合将引发 NotSupportedException。
请注意,.NET 页编译器在页编译时自动将成员引用插入存储在 StaticObjects 集合中的所有对象,以便开发人员可以在页请求时访问这些应用程序对象,而无需引用 Application 集合。例如:
<html> </body> Application Level Title: <%= MyInfo.Title %> <body> </html>
应用程序状态同步
应用程序中的多个线程可以同时访问存储在应用程序状态中的值。因此,当您创建需要访问应用程序状态值的对象时,必须始终确保该应用程序状态对象是自由线程的并执行它自己的内部同步,要不就执行手动同步步骤以防止出现争用状态、死锁或访问冲突。
HttpApplicationState 类提供两种方法 Lock 和 Unlock,一次只允许一个线程访问应用程序状态变量。
对 Application 对象调用 Lock 会导致 ASP.NET 阻止运行在其他辅助线程上的代码试图访问应用程序状态中的任何对象。只有当调用 Lock 的线程对 Application 对象调用相应的 Unlock 方法时才解除对这些线程的阻塞。
下面的代码示例演示使用锁定以防止出现争用状态。
[Visual Basic] ' Visual Basic code from within a page, a handler, or Global.asax. Application.Lock() Application("SomeGlobalCounter") = _ CType(Application("SomeGlobalCounter"), Integer) + 1 Application.UnLock() [C#] // C# code from within a page, a handler, or Global.asax. Application.Lock(); Application["SomeGlobalCounter"] = (int)Application["SomeGlobalCounter"] + 1; Application.UnLock();
如果没有显式调用 Unlock,当请求完成、请求超时或请求执行过程中出现未处理的错误并导致请求失败时,.NET Framework 将自动移除锁。这种自动取消锁定会防止应用程序出现死锁。
ASP.NET 提供 Web 应用程序需要的跨请求状态信息(购物车、数据滚动等)基础结构,并带有内置的会话状态功能,使您可以采取以下操作:
- 对从单个浏览器客户端到服务器上逻辑应用程序会话的请求进行自动识别和分类。
- 将会话范围的数据存储在服务器上以供跨多个浏览器请求使用。
- 引发适当的可在应用程序代码中处理的会话生存期管理事件(Session_OnStart、Session_OnEnd 等)。
注意 Session_OnEnd 事件仅支持进程内会话状态模式。如果您使用状态服务器或 SQL Server 模式,则不会引发该事件。
- 如果浏览器不在指定的超时时间内重新访问应用程序,则自动释放会话数据。
本主题提供会话状态的概述,介绍如何标识和跟踪活动 ASP.NET 会话;解释会话状态存储和一般结构,并以一个高级代码示例作为总结。
会话状态概述
HTTP 是一个无状态的协议,这意味着它不自动指示一个请求序列是否都来自相同的客户端,甚至不指示单个浏览器实例是否仍活跃地查看某个页或站点。因此,如果没有其他基础结构的帮助,要想生成需要维护某些跨请求状态信息的 Web 应用程序,如购物车、数据滚动等,就可能会非常困难。
ASP.NET 提供以下会话支持:
- 便于使用的会话状态功能,该功能是 ASP 开发人员所熟悉的,与其他 .NET Framework API 兼容。
- 可靠的会话状态功能,可以经受得住 Internet 信息服务 (IIS) 重新启动和辅助进程重新启动而不丢失会话数据。
- 可缩放的会话状态功能,该功能可用于网络场(多计算机)和网络园(多进程)两种情况,使管理员可以将更多的处理器分配给 Web 应用程序以提高它的可缩放性。
- 用于不支持 HTTP Cookie 的浏览器的会话状态功能。
- 对于核心会话状态方案,其吞吐量相当于(或高于)ASP 的吞吐量(当向购物车放入项时 50/50 读/写,修改访问的最后一页,验证信用卡详细信息等)。
但是,会话状态不跨 Web 应用程序边界保持。如果执行期间一个 Web 应用程序切换到另一个应用程序,则会话信息不能用于新应用程序。
标识会话
每个活动的 ASP.NET 会话都是使用 120 位的 SessionID 字符串进行标识和跟踪的,该字符串只包含 URL 中所允许使用的 ASCII 字符。SessionID 值是使用保证唯一性和随机性的算法生成的,其中保证唯一性的目的是确保会话不冲突,保证随机性的目的是确保怀有恶意的用户不能使用新的 SessionID 来计算现有会话的 SessionID。
根据配置应用程序设置的方式,通过 HTTP Cookie 或嵌套有 SessionID 字符串的修改的 URL 跨客户端-服务器请求与 SessionID 字符串进行通信。
会话状态存储
ASP.NET 提供一个简单、易于使用的会话状态模型,您可以使用该模型跨多个 Web 请求存储任意数据和对象。它使用基于字典的、内存中的对象引用(这些对象引用存在于 IIS 进程中)缓存来完成该操作。使用进程内会话状态模式时请考虑下面的限制:
- 使用进程内会话状态模式时,如果 aspnet_wp.exe 或应用程序域重新启动,则会话状态数据将丢失。这些重新启动通常会在下面的情况中发生:
- 在应用程序的 Web.config 文件的 <processModel> 元素中,设置一个导致新进程在条件被满足时启动的属性,例如 memoryLimit。
- 修改 Global.asax 或 Web.config 文件。
- 更改到 Web 应用程序的 \Bin 目录。
- 用杀毒软件扫描并修改 Global.asax 文件、Web.config 文件或 Web 应用程序的 \Bin 目录下的文件。
- 如果在应用程序的 Web.config 文件的 <processModel> 元素中启用了网络园模式,请不要使用进程内会话状态模式。否则将发生随机数据丢失。
在进程外模式中,.NET 状态服务器不是保留活动对象,而是将会话状态存储在内存中。在这种模式中,辅助进程直接与状态服务器对话。在 SQL 模式中,会话状态存储在 SQL Server 数据库中,辅助进程直接与 SQL 对话。ASP.NET 辅助进程这时能够利用该简单的存储服务,方法是在每个 Web 请求结束时在客户端的 Session 集合中(使用 .NET 序列化服务)序列化并保存所有对象。当客户端重新访问服务器时,相关的 ASP.NET 辅助进程从状态服务器中以二进制流的形式检索这些对象,将它们反序列化为实时实例,并将它们放置回对请求处理程序公开的新 Session 集合对象。
在 SQL 模式中,也可以将会话状态配置为在故障转移群集中工作。故障转移群集是两个或更多相同的冗余 Web 服务器,它们将会话数据存储在一台单独的计算机上的 SQL Server 数据库中。有关如何设置此配置的信息,请参见配置 SQL Server 模式。
通过有效地将会话数据的存储与应用程序对它的使用分开,ASP.NET 支持许多功能强大的方案,而这些方案对于 ASP 的早期版本是不可用的:
- 因为用于会话状态的内存不在 ASP.NET 辅助进程中,所以可以实现从应用程序故障的恢复。
因为所有状态与个别辅助进程不存储在一起,所以如果由于访问冲突导致进程故障,或者在出现死锁或内存泄漏的情况下进程被 IIS 管理服务强行重新启动,状态也不会丢失。
- 跨多个辅助进程对应用程序进行分区。
因为所有状态与辅助进程不存储在一起,您可以干净地跨多个进程对应用程序进行分区。这种分区可以显著地提高多个进程的计算机上应用程序的可用性和可缩放性。此外,因为它将每个辅助进程与单个计算机关联起来,所以 ASP.NET 能够消除跨处理器锁争用,这是 ASP 早期版本中主要的可缩放性瓶颈之一。
- 跨多个网络场计算机对应用程序进行分区。
因为所有状态与辅助进程不存储在一起,所以您可以跨运行于多个计算机上的多个辅助进程对应用程序进行分区。我们有时需要在运行于不同计算机上的辅助进程和状态服务间传达状态,有时需要在运行于相同计算机上的进程和服务器间传达状态,而这两种传达状态的模型几乎是相同的。不管是哪种情况,每个网络场只能有一个状态服务器。
会话状态结构
基于 ASP.NET 的应用程序使用基于事件的执行组织启用多个 .NET Framework 类模块来参与单个 Web 请求的处理。
SessionState 模块
.NET Framework 通过 SessionStateModule 类(从 IHttpModule 派生)实现会话状态,该类参与基于 .NET 的应用程序所接收的每个请求的执行。SessionStateModule 负责生成或获得唯一的 SessionID 字符串,并负责存储状态数据和从外部状态提供程序检索状态数据。
会话状态集合
SessionState 类公开两个状态集合:Contents 和 StaticObjects。Contents 集合公开已直接通过代码添加到会话状态集合的所有变量项。例如:
[Visual Basic] ' Visual Basic code from within a page, a handler, or Global.asax. Session("Message") = "MyMsg" Session("AppStartTime") = Now [C#] // C# code from within a page, a handler, or Global.asax. Session["Message"] = "MyMsg"; Session["AppStartTime"] = DateTime.Now;
为了与 ASP 的早期版本兼容,还可以通过应用程序对象上的 Contents 属性访问这些值,如下面的示例所示。
[Visual Basic] ' Visual Basic code from within a page, a handler, or Global.asax. Session.Contents("Message") = "MyMsg" Session.Contents("AppStartTime") = Now [C#] // C# code from within a page, a handler, or Global.asax. Session.Contents["Message"] = "MyMsg"; Session.Contents["AppStartTime"] = DateTime.Now;
StaticObjects 集合公开所有已经通过 Global.asax 文件中带有“Session”范围的 <object runat="server"> 标记添加到会话状态集合的变量项。例如:
' Global.asax definition. <OBJECT RUNAT="SERVER" SCOPE="SESSION" ID="MyInfo" PROGID="Scripting.Dictionary"> </OBJECT>
对象不能从 ASP.NET 应用程序内的其他任何地方添加到 StaticObjects 集合中。如果用户试图直接通过代码添加对象,则集合将引发一个 NotSupportedException。
注意 ASP.NET 页编译器在页编译时自动将成员引用插入到存储在 StaticObjects 集合中的所有对象中。
页开发人员可以在页请求时直接访问 Session 对象,而无需通过 StaticObjects 集合,如下面的示例所示:
<html> </body> Number of entries: <%= MyInfo.Count %> <body> </html>
会话状态的配置和启动
ASP.NET 中有三种会话状态模式。您可以在进程内、状态服务器和 SQL Server 之间选择。不管选择何种模式,基本的配置过程都是一样的。
ASP.NET 通过两个阶段配置会话状态。首先,将会话状态模块插入 HTTP 请求。默认情况下,这是在整个计算机 Machine.config 文件中配置层次结构的根完成的。
下面的示例显示了 Machine.config 文件中的一个示例项。为使配置文件正常工作,您必须为适当的 System.Web.SessionState.SessionStateModule 程序集版本提供完全限定程序集名称。此版本通常是与应用程序使用的 .NET Framework 版本关联的版本。有关如何获取完全限定程序集名称的信息,请参见程序集名称。
<httpmodules> ... <!-- You must supply a valid fully qualified assembly name here. --> <!-- For this example to work correctly, the version number for --> <!-- the referenced assemby must match the version installed on --> <!-- your computer by the .NET Framework. --> <add name="sessionState" type="System.Web.SessionState.SessionStateModule, Version=1.0.3300.0,Culture=neutral,PublicKeyToken=b77a5c561934e089" /> ... </httpmodules>
然后,根据要使用的会话状态,在 <sessionState> 配置元素中设置适当的会话状态服务属性。
配置进程内模式
进程内模式是默认的会话状态模式。若要使用进程内模式,请将 <sessionState> 元素的 mode 属性设置为 Inproc
。
下面显示了进程内模式的一个配置设置示例。
<configuration> <system.web> <sessionState mode="Inproc" cookieless="false" timeout="20"/> </sessionState> </system.web> </configuration>
配置状态服务器模式
若要使用状态服务器,必须首先确保 ASP.NET 状态服务运行在用于会话存储的远程服务器上。此服务与 ASP.NET 和 Visual Studio .NET 一起安装在以下位置:
systemroot\Microsoft.NET\Framework\versionNumber\aspnet_state.exe
然后,在应用程序的 Web.config 文件中,将 <sessionState> 元素的 mode 属性设置为 StateServer。最后,将 connectionString 属性设置为 tcpip=serverName:portNumber。
下面是状态服务器模式的一个配置设置示例。
<configuration>
<system.web>
<sessionState mode="StateServer"
stateConnectionString="tcpip=dataserver:42424
"
cookieless="false"
timeout="20"/>
</sessionState>
</system.web>
</configuration>
配置 SQL Server 模式
若要使用 SQL Server,首先在将存储会话状态的 SQL Server 计算机上,运行 InstallSqlState.sql 或 InstallPersistSqlState.sql。两个脚本均创建一个名为 ASPState 的数据库,它包含若干存储过程。两个脚本间的差异在于放置 ASPStateTempApplications 和 ASPStateTempSessions 表的位置。InstallSqlState.sql 脚本将这些表添加到 TempDB 数据库,该数据库在计算机重新启动时将丢失数据。相反,InstallPersistSqlState.sql 脚本将这些表添加到 ASPState 数据库,该数据库允许在计算机重新启动时保留会话数据。
默认情况下,两个脚本文件均安装在下面的位置:
systemroot\Microsoft.NET\Framework\versionNumber
然后,在应用程序的 Web.config 文件中,将 <sessionState> 元素的 mode 属性设置为 SQLServer。最后,将 sqlConnectionString 属性设置为 Integrated Security=SSPI;data source=serverName;。
下面显示了 SQL Server 模式的一个配置设置示例。
<configuration>
<system.web>
<sessionState mode="SQLServer"
sqlConnectionString=" Integrated Security=SSPI;data source=dataserver;
"
cookieless="false"
timeout="20"/>
</sessionState>
</system.web>
</configuration>
在 SQL Server 模式中,也可以将会话状态配置为在故障转移群集中工作。故障转移群集是两个或更多相同的冗余 Web 服务器,它们将会话数据存储在一台单独的计算机上的 SQL Server 数据库中。如果一个 Web 服务器出现故障,群集中的另一个服务器会接管它的工作,为请求提供服务,会话数据不会丢失。若要配置故障转移群集,请将 Web 服务器的 Web.config 文件中的 <machinekey> 元素设置为相同的值。然后将 Web 服务器的 SQL 连接字符串设置为指向计算机上存储会话数据的 SQL Server 数据库。
高级代码示例
下面的示例显示如何以只读方式访问现有的会话状态数据,以动态生成包含用户信息和个人股票证券信息的页。
[Visual Basic] <%@ Language=VB EnableSessionState=true %> <html> <head> <script runat="server"> Sub Page_Load(ByVal Sender as Object, ByVal E as EventArgs) ' Obtain data table of user's personal stock data. Dim MyStocks as DataTable Dim Stock as DataRow MyStocks = _ CType(Session("PersonalStockData"), DataTable) ' Update HTML output with session values. Name.InnerText = Session("FirstName").ToString() SpouseVal.InnerText = Session("SpouseName").ToString() For Each Stock In MyStocks.Rows StockList.AddItem(Stock("Symbol") & ": " & Stock("Name")) Next End Sub </script> </head> <body> Hi <span id="Name" runat=server/>, your spouse is: <span id="SpouseVal" runat="server"/>. Here are the stocks you and your spouse currently own: <acme:listbox id="StockList" runat="server"> <! — List box is dynamically populated from code. --> </acme:listbox> </body> </html> [C#] <%@ Language=C# EnableSessionState=true %> <html> <head> <script runat=server> void Page_Load(Object Sender, EventArgs E) { // Obtain data table of user's personal stock data. DataTable MyStocks = (DataTable)Session["PersonalStockData"]; // Update HTML output with session values. Name.InnerText = Session["FirstName"].ToString(); SpouseVal.InnerText = Session["SpouseName"].ToString(); foreach (DataRow Stock in MyStocks.Rows) { StockList.AddItem(Stock["Symbol"] + ": " + Stock["Name"]); } } </script> </head> <body> Hi <span id="Name" runat="server"/>, your spouse is: <span id="SpouseVal" runat="server"/>. Here are the stocks you and your spouse currently own: <acme:listbox id="StockList" runat="server"> <! — List box is dynamically populated from code. --> </acme:listbox> </body> </html>当创建 Web 窗体应用程序时,经常需要将信息从一个 Web 窗体页传递到另一个 Web 窗体页。这允许在一个 Web 窗体页上输入信息,然后将该信息提交到另一个页进行处理。
使用内联代码在 Web 窗体页间传递值的设计模式与使用代码隐藏文件的设计模式稍微有所不同。选择哪种设计模式取决于您是喜欢使用内联代码还是代码隐藏文件。将在下面的节中对两种设计模式进行讨论。
使用内联代码的设计模式
当使用代码内联将值传递到另一个 Web 窗体页时,您首先需要为包含所要发送信息的 Web 窗体页指定类名。通过在 @ Page 指令中包括 ClassName 属性和类的名称为该 Web 窗体页指定类名。然后,在该类中为要共享的每个值创建一个具有 get 访问器的属性。get 访问器应返回您要传递的值(例如文本框的值)。若要发送这些信息,请使用 Server 对象的 Transfer 方法将应用程序的控制传输到其他 Web 窗体页。
在接收 Web 窗体页上,通过在页的顶部添加一个 @Reference 指令并将 Page 属性设置为发送页来引用发送页中声明的类。然后,接收 Web 窗体页可以通过首先检索处理程序的实例来访问信息,该处理程序首先从 HttpContext 对象的 Handler 属性接收到 HTTP 请求。然后,处理程序对象将转换为封装所传递信息的类的实例。该转换一旦执行,就可以通过转换后对象的属性访问所传递的值。
警告 请勿在页面之间传送敏感信息(如信用卡信息或密码)。创建将值发送到另一个 Web 窗体页的 Web 窗体页
- 通过在 Web 窗体页的顶部添加 @ Page 指令并将 ClassName 属性设置为有效的类名来为源 Web 窗体页指定类名。
[Visual Basic] <%@ Page Language="VB" ClassName="MyClassName" %> [C#] <%@ Page Language="C#" ClassName="MyClassName" %>- 在该类中为要传递到另一个 Web 窗体页的每个值都定义一个具有 get 访问器的属性。get 访问器应只返回您要传递的值(例如 Web 窗体页上文本框的值)。必须在服务器端脚本中定义这些属性。
[Visual Basic] <script runat="server"> Public ReadOnly Property FirstName() As String Get ' FirstNameTextBox is the name of a TextBox control. Return FirstNameTextBox.Text End Get End Property </script> [C#] <script runat="server"> public string FirstName { get { // FirstNameTextBox is the name of a TextBox control. return FirstNameTextBox.Text; } } </script>- 当要将信息传递到另一个 Web 窗体页时(例如当单击了按钮时),使用 HttpServerUtility.Transfer 方法结束在当前页的执行并将应用程序的控制传输到另一个 Web 窗体页。HttpServerUtility.Transfer 方法采用单个参数,该参数允许您指定要将控制传输到的 Web 窗体页的 URL。
[Visual Basic] Sub SubmitButtonClick(sender As Object, e As EventArgs) Server.Transfer("secondpage.aspx") End Sub [C#] void SubmitButtonClick(object sender, EventArgs e) { Server.Transfer("secondpage.aspx"); }下面是一个完整的示例,显示如何使用内联代码创建 Web 窗体页以将两个 TextBox 控件的值传递到另一个 Web 窗体页。该示例的名称必须是 Firstpage.aspx。
[Visual Basic] <%@ Page Language="VB" ClassName="FirstPageClass" %> <html> <head> <script runat="server"> Public ReadOnly Property FirstName() As String Get ' first is the name of a TextBox control. Return first.Text End Get End Property Public ReadOnly Property LastName() As String Get ' last is the name of a TextBox control. Return last.Text End Get End Property Sub ButtonClicked(sender As Object, e As EventArgs) Server.Transfer("secondpage.aspx") End Sub </script> </head> <body> <form runat="server"> First Name: <asp:TextBox id="first" runat="server"/> <br> Last Name: <asp:TextBox id="last" runat="server"/> <br> <asp:Button OnClick="ButtonClicked" Text="Go to second page" runat=server /> </form> </body> </html> [C#] <%@ Page Language="C#" ClassName="FirstPageClass" %> <html> <head> <script runat="server"> public string FirstName { get { return first.Text; } } public string LastName { get { return last.Text; } } void ButtonClicked(object sender, EventArgs e) { Server.Transfer("secondpage.aspx"); } </script> </head> <body> <form runat="server"> First Name: <asp:TextBox id="first" runat="server"/> <br> Last Name: <asp:TextBox id="last" runat="server"/> <br> <asp:Button OnClick="ButtonClicked" Text="Go to second page" runat=server /> </form> </body> </html>创建从另一个 Web 窗体页接收值的 Web 窗体页
- 在接收信息的 Web 窗体页上,在页的顶部添加 @Reference 指令并将 Page 属性设置为源 Web 窗体页(包含您要传递信息的 Web 窗体页)。
<%@ Reference Page="firstpage.aspx" %>- 在服务器端脚本中声明一个变量,以存储在发送信息的 Web 窗体页中定义的类的实例。
[Visual Basic] <script runat="server"> Dim fp As FirstPageClass </script> [C#] <script runat="server"> FirstPageClass fp; </script>- 创建一个自定义 Page_Load 事件处理程序,当 Web 窗体页不回发到其本身时,该处理程序将当前 HTTP 请求的 IHttpHandler 实现的对象分配给上一步中声明的变量。使用 IsPostBack 属性可确定是否将该页回发到其本身。IHttpHandler 实现的对象包含首先接收 HTTP 请求的处理程序的实例。因为 IHttpHandler 实现的对象与上一步声明的变量属于不同的对象类型,所以必须首先将它转换为封装从第一个 Web 窗体页发送的信息的类,然后才可以将它指派给该变量。使用 HttpContext 对象的 Handler 属性来检索处理程序对象。
[Visual Basic] <script runat="server"> Sub Page_Load() If Not IsPostBack Then fp = CType(Context.Handler, FirstPageClass) End If End Sub </script> [C#] <script runat="server"> void Page_Load() { if (!IsPostBack) { fp = (FirstPageClass)Context.Handler; } } </script>- 第二步中声明的变量现在包含封装上一个 Web 窗体页中信息的类的实例。使用该变量访问类(该类包含从上一个 Web 窗体页发送的信息)的属性。可以通过编程方式访问这些值以执行计算,或者只是使用脚本分隔符 <%= 和 %> 来显示它们。
Hello <%=fp.FirstName%>下面显示一个完整的 Web 窗体页,该页从另一个 Web 窗体页接收两个值。然后,这些值显示在 Web 窗体页上。您必须将该示例叫做 Secondpage.aspx。
[Visual Basic] <%@ Page Language="VB" %> <%@ Reference Page="firstpage.aspx" %> <html> <head> <script runat="server"> Dim fp As FirstPageClass Sub Page_Load() If Not IsPostBack Then fp = CType(Context.Handler, FirstPageClass) End If End Sub </script> </head> <body> <form runat="server"> Hello <%=fp.FirstName%> <%=fp.LastName%> </form> </body> </html> [C#] <%@ Page Language="C#" %> <%@ Reference Page="firstpage.aspx" %> <html> <head> <script runat="server"> FirstPageClass fp; void Page_Load() { if (!IsPostBack) { fp = (FirstPageClass)Context.Handler; } } </script> </head> <body> <form runat="server"> Hello <%=fp.FirstName%> <%=fp.LastName%> </form> </body> </html>使用代码隐藏文件的设计模式
当使用代码隐藏文件时,代码隐藏文件包含与 Web 窗体页关联的代码的类声明。在发送信息的 Web 窗体页的代码隐藏文件中,首先在类中为要共享的每个值创建一个具有 get 访问器的属性。get 访问器应返回您要传递的值(例如文本框的值)。若要发送这些信息,请使用 Server 对象的 Transfer 方法将应用程序的控制传输到其他 Web 窗体页。
在接收 Web 窗体页上,通过在页的顶部添加 @Reference 指令并将 Page 属性设置为发送页来引用在发送页中声明的类。然后您可以检索处理程序的实例,该处理程序首先从 HttpContext 对象的 Handler 属性接收 HTTP 请求。
注意 若要使在发送 Web 窗体页中声明的类在接收 Web 窗体页的代码隐藏文件中可用,您必须使用命令行编译器将每个 Web 窗体页的代码隐藏文件手动编译为单个 .dll 文件。该 .dll 文件必须放置在 Web 窗体应用程序的 \Bin 目录中。然后,处理程序对象将转换为封装所传递信息的类的实例。该转换一旦执行,就可以通过转换后对象的属性访问所传递的值。
警告 请勿在页面之间传送敏感信息(如信用卡信息或密码)。从不同的 Web 窗体页发送服务器控件值
- 为发送 Web 窗体页创建代码隐藏文件,此文件包含与该页关联的代码的类声明。
[Visual Basic] Imports System ' Add other references here. Public Class FirstPageClass Inherits System.Web.UI.Page ' Add class code here. End Class [C#] Imports System // Add other references here. public class FirstPageClass : System.Web.UI.Page { // Add class code here. }- 若要在代码隐藏文件声明的类中访问 Web 窗体页上的服务器控件,请在表示要访问的服务器控件的类中声明受保护的变量。
[Visual Basic] Protected FirstNameTextBox As System.Web.UI.WebControls.TextBox [C#] protected System.Web.UI.WebControls.TextBox FirstNameTextBox;- 在第一步声明的类中,为要传递到另一个 Web 窗体页的每个值都定义一个具有 get 访问器的属性。get 访问器应只返回您要传递的值(例如 Web 窗体页上文本框的值)。
[Visual Basic] Public ReadOnly Property FirstName() As String Get ' FirstNameTextBox is the name of a TextBox control. Return FirstNameTextBox.Text End Get End Property [C#] public string FirstName { get { // FirstNameTextBox is the name of a TextBox control. return FirstNameTextBox.Text; } }- 当要将信息传递到另一个 Web 窗体页时(例如当单击按钮时),使用 HttpServerUtility.Transfer 方法结束在当前页上的执行并将应用程序的控制传输到另一个 Web 窗体页。HttpServerUtility.Transfer 方法采用单个参数,该参数允许您指定要将控制传输到的 Web 窗体页的 URL。
[Visual Basic] Sub SubmitButtonClick(sender As Object, e As EventArgs) Server.Transfer("secondpage.aspx") End Sub [C#] void SubmitButtonClick(object sender, EventArgs e) { Server.Transfer("secondpage.aspx"); }- 为发送信息的 Web 窗体页创建界面。确保将 Inherits 属性添加到 @ Page 指令,并将其设置为在代码隐藏文件中声明的类。
[Visual Basic] <%@ Page Language="VB" Inherits="FirstPageClass" %> <html> <head> </head> <body> <!-- Add User Interface here --> </body> [C#] <%@ Page Language="C#" Inherits="FirstPageClass" %> <html> <head> </head> <body> <!-- Add User Interface here --> </body>下面显示代码隐藏文件的一个完整的示例,该代码隐藏文件与发送信息的 Web 窗体页关联。根据您是使用 Visual Basic 还是 C#,确保该示例分别名为 Firstpage.aspx.vb 或 Firstpage.aspx.cs。
[Visual Basic] Imports System Public Class FirstPageClass : Inherits System.Web.UI.Page Protected first As System.Web.UI.WebControls.TextBox Protected last As System.Web.UI.WebControls.TextBox Protected Button1 As System.Web.UI.WebControls.Button Public ReadOnly Property FirstName() As String Get ' first is the name of a TextBox control. Return first.Text End Get End Property Public ReadOnly Property LastName() As String Get ' last is the name of a TextBox control. Return last.Text End Get End Property Sub ButtonClicked(sender As Object, e As EventArgs) Server.Transfer("secondpage.aspx") End Sub End Class [C#] using System; public class FirstPageClass : System.Web.UI.Page { protected System.Web.UI.WebControls.TextBox first; protected System.Web.UI.WebControls.TextBox last; protected System.Web.UI.WebControls.Button Button1; public string FirstName { get { return first.Text; } } public string LastName { get { return last.Text; } } void ButtonClicked(object sender, EventArgs e) { Server.Transfer("secondpage.aspx"); } }下面是一个完整的示例,显示如何用代码隐藏文件创建 Web 窗体页来将两个 TextBox 控件的值传递到另一个 Web 窗体页。确保该示例名为 Firstpage.aspx。
[Visual Basic] <%@ Page Language="VB" Inherits="FirstPageClass" %> <html> <head> </head> <body> <form runat="server"> First Name: <asp:TextBox id="first" runat="server"/> <br> Last Name: <asp:TextBox id="last" runat="server"/> <br> <asp:Button id="Button1" OnClick="ButtonClicked" Text="Go to second page" runat=server /> </form> </body> </html> [C#] <%@ Page Language="C#" Inherits="FirstPageClass" %> <html> <head> </head> <body> <form runat="server"> First Name: <asp:TextBox id="first" runat="server"/> <br> Last Name: <asp:TextBox id="last" runat="server"/> <br> <asp:Button id="Button1" OnClick="ButtonClicked" Text="Go to second page" runat=server /> </form> </body> </html>从不同的 Web 窗体页接收服务器控件值
- 为接收 Web 窗体页创建一个代码隐藏文件,该文件包含与该页关联的代码的类声明。
[Visual Basic] Imports System ' Add other references here. Public Class SecondPageClass Inherits System.Web.UI.Page ' Add class code here. End Class [C#] Imports System // Add other references here. public class SecondPageClass : System.Web.UI.Page { // Add class code here. }- 若要访问代码隐藏文件中 Web 窗体页上的服务器控件,请在表示要访问的服务器控件的类中声明受保护的变量。
[Visual Basic] Protected FirstNameTextBox As System.Web.UI.WebControls.TextBox [C#] protected System.Web.UI.WebControls.TextBox FirstNameTextBox;- 在类中声明变量以存储发送信息的 Web 窗体页中定义的类的实例。如果要使该变量在接收 Web 窗体页中可用,则将其设为公共的。
注意 在将每个 Web 窗体页的代码隐藏文件编译为单个 .dll 文件之前,不能访问发送信息的 Web 窗体页中定义的类。该 .dll 文件必须放置在 Web 应用程序的 \Bin 目录中。该过程在稍后的步骤中有所介绍。[Visual Basic] Public fp As FirstPageClass [C#] public FirstPageClass fp;- 创建一个自定义 Page_Load 事件处理程序,(当 Web 窗体页不回发到其本身时)该处理程序将当前 HTTP 请求的 IHttpHandler 实现的对象分配给上一步中声明的变量。使用 IsPostBack 属性可确定是否将该页回发到其本身。IHttpHandler 实现的对象包含首先接收 HTTP 请求的处理程序的实例。因为 IHttpHandler 实现的对象与上一步声明的变量属于不同的对象类型,所以必须首先将它转换为封装从第一个 Web 窗体页发送的信息的类,然后才可以将它指派给该变量。使用 HttpContext 对象的 Handler 属性来检索处理程序对象。
[Visual Basic] Sub Page_Load() If Not IsPostBack Then fp = CType(Context.Handler, FirstPageClass) End If End Sub [C#] void Page_Load() { if (!IsPostBack) { fp = (FirstPageClass)Context.Handler; } }- 第三步中声明的变量现在包含封装上一个 Web 窗体页中信息的类的实例。使用该变量访问从上一个 Web 窗体页发送的信息。可以通过编程方式访问这些值以执行计算,或者只是使用脚本分隔符 <%= 和 %> 来显示它们。
Hello <%=fp.FirstName%>- 为发送信息的 Web 窗体页创建界面。在 @ Page 指令中,确保添加设置为代码隐藏文件中声明的类的 Inherits 属性。
[Visual Basic] <%@ Page Language="VB" Inherits="SecondPageClass" %> <html> <head> </head> <body> <!-- Add User Interface here --> </body> [C#] <%@ Page Language="C#" Inherits="SecondPageClass" %> <html> <head> </head> <body> <!-- Add User Interface here --> </body>- 在接收信息的 Web 窗体页的顶部添加 @Reference 指令,并将 Page 属性设置为源 Web 窗体页(包含要传递信息的 Web 窗体页)。
<%@ Reference Page="firstpage.aspx" %>- 发送和接收 Web 窗体页在这时都已完成。但是,在应用程序可以正常运行之前,必须将每个页的代码隐藏文件编译为单个 .dll 文件。该 .dll 文件必须放置在 Web 应用程序的 \Bin 目录中。这允许在接收 Web 窗体页中可以访问发送 Web 窗体页中声明的类。若要编译代码隐藏文件,请在命令行输入以下命令:
[Visual Basic] vbc /out:Bin\appname.dll /r:System.dll /r:System.Web.dll /t:library firstpage.aspx.vb secondpage.aspx.vb [C#] csc /out:Bin\appname.dll /r:System.dll /r:System.Web.dll /t:library firstpage.aspx.cs secondpage.aspx.cs下面显示代码隐藏文件的一个完整的示例,该代码隐藏文件与接收信息的 Web 窗体页关联。根据您是使用 Visual Basic 还是 C#,确保该示例分别名为 Secondpage.aspx.vb 或 Secondpage.aspx.cs。
[Visual Basic] Imports System Public Class SecondPageClass Inherits System.Web.UI.Page Protected DisplayLabel As System.Web.UI.WebControls.Label Public fp As FirstPageClass Sub Page_Load() If Not IsPostBack Then fp = CType(Context.Handler, FirstPageClass) End If End Sub End Class [C#] using System; public class SecondPageClass : System.Web.UI.Page { protected System.Web.UI.WebControls.Label DisplayLabel; public FirstPageClass fp; void Page_Load() { if (!IsPostBack) { fp = (FirstPageClass) Context.Handler; } } }下面是一个完整的示例,显示如何用代码隐藏文件创建 Web 窗体页,以接收从另一个 Web 窗体页传递的值。确保该示例名为 Secondpage.aspx。
[Visual Basic] <%@ Page Language="VB" Inherits="SecondPageClass" %> <%@ Reference Page="firstpage.aspx" %> <html> <head> </head> <body> <form runat="server"> Hello <%=fp.FirstName%> <%=fp.LastName%> </form> </body> </html> [C#] <%@ Page Language="C#" Inherits="SecondPageClass" %> <%@ Reference Page="firstpage.aspx" %> <html> <head> </head> <body> <form runat="server"> Hello <%=fp.FirstName%> <%=fp.LastName%> </form> </body> </html>