3.3.3 使用Application和Session的事件 ASP的Application和Session对象体现了其他ASP内置对象所没有的特征——事件。然而,正像在前面的对象成员表中看到的那样,这些都是ASP会话和应用程序的工作相联系的事件。 1. Application和Session的事件处理器 每当一个应用程序或会话启动或结束时,ASP触发一个事件。可以通过在一个特殊的文件中编写普通的脚本代码来检测和应答这些事件,这个文件名为global.asa,位于一个应用程序的根目录中(对于缺省的Web网站是InetPubWWWRoot目录,或是作为一个实际应用程序定义的一个文件夹)。这个文件可以包含一个或多个HTML的<OBJECT>元素,用于创建将在该应用程序或用户会话内使用的组件实例。 在第4章中将详细地介绍如何创建组件实例。下面的代码是global.asa文件的一个例子。我们只关注<OBJECT>元素以及以Set关键字开始的那些代码行: <!-- Declare instance of the ASPCounter component with application-level scope //--> <OBJECT ID=”ASPCounter” RUNAT=”Server” SCOPE=”Application” PROGID=”MSWC.Counters”> </OBJECT>
<!-- Declare instance of the ASPContentLimk component with session-level scope //--> <OBJECT ID=”ASPContentLink” RUNAT=”Server” SCOPE=”Session” PROGID=”MSWC.NextLink”> </OBJECT>
<SCRIPT LANGUAGE=”VBScript” RUNAT=”Server”>
Sub Application_onStart() ‘Create an instance of an ADO Recordset with application-level scope Set Application(“ADOConnection”) _ = Server.CreateObject(“ADODB.Connection”) Dim varArray(3) ‘Create a Variant array and fill it VarArray(0) = “This is a” VarArray(1) = “Variant array” VarArray(2) = “stored in the” VarArray(3) = “Application object” Application(“Variant_Array”) = varArray‘Store it in the Application Application(“Start_Time”) = CStr(Now) ‘Store the date/time as a string Application(“Visit_Count”) = 0 ‘Set Counter variable to zero End Sub
Sub Application_onEnd() Set Application(“ADOConnection”) = Nothing End Sub
Sub Sesson_onStart() ‘Create an instance of the AdRotator component with session-level scope Set Session(“ASPAdRotator”) = Server.CreateObject(“MSWC.AdRotator”) Dim varArray(3) ‘Create a Variant arry and fill it VarArray(0) = “This is a” VarArray(1) = “Variant array” VarArray(2) = “stored in the” VarArray(3) = “Session object” Session(“Variant_Array”) = varArray ‘Store it in the Session Session(“Start_Time”) = CStr(Now) ‘Store the date/time as a string
‘We can access the contents of the Request and Response in a Session_onStart ‘event handler for the page that initiated the session. This is the *only* ‘place that the ASP page context is available like this. ‘as an example, we can get the IP address of the user: Session(“Your_IP_Address”) = Request.ServerVariables(“REMOTE_ADDR”) Application.Lock intVisits = Application(“Visit_Count”) +1 Application(“Visit_Count”) = intVisits Application.Unlock End Sub
Sub Session_onEnd() Set Session(“ASPAdRotator”) = Nothing End Sub </SCRIPT> 因为这个global.asa文件用于本章中的示例页面,所以将需要将该文件放到Web网站的根目录中,或者放到已配置为一个虚拟应用程序的目录中,并且在该目录中包含有其他示例文件。 读取和存储值 注意上面的例子怎样读取Application和Session的变量,与在Request和Response对象的集合中所采取的方式相同。设置这些变量的值: Application(“variable_name”) = variable_value Application(“variable_name”) = variant_array_variable_name Set Application(“variable_name”) = object_reference 获取这些变量的值: variable_value = Application(“variable_name”) variant_array_variable = Application(“variable_name”) Set object_reference = Application(“variable_name”) 当然,对于Session对象可采取同样的方法。 可以看到,当从一个Session事件处理器访问时,怎样“锁定”(Lock)和“解锁”(unlock)该Application对象;当从一个ASP网页访问时,需要进行相同的处理。用Application事件内的代码访问Application对象中的值时,不要求这么做。这是因为在任何应用程序中只有一个Application对象的实例,并且其事件处理器的代码只在没有活动的用户会话时进行。 也可以看到一个基本的用户会话计数器是如何实现的。这里使用一个应用程序级的变量Visit_count,当新的会话启动时它就自动增加。 一般也不限制简单地把值保存到Application或Session对象中。例如,Web开发者的Web站点在http://webdev.wrox.co.uk上,有相应的一个global.asa文件,当一个新的会话启动时该文件就在服务器上的数据库中写入相应的条目,数据细节从Request.ServerVariables集合中获取。这提供了一个基本的方法统计访问者的数量,并收集访问者的一些基本信息。 2. 创建Variant数组 在Session和Application对象中创建和使用一个Variant数组来存储值的方法目前尚未讨论,在这里作为一个非常有用的技术进行讨论。正如已经看到的那样,一个Variant数据类型可以包含一个数组,而不仅仅是一个值。 一个数组只是在内存的一个连续区域中以指定的次序存储二进制值的一个长行。要安排Variant,需要指向首项,并给出有关大小和结构的信息,脚本引擎可以做余下的事情。 可在一个Variant变量中创建一维、二维或多维数组,然后把该数组分配给一个应用程序层或用户会话层的变量,并保证整个数组可在相应的地方使用。下面代码演示了一个简单的一维数组的使用技术: Dim varArray(3) varArray(0) = “This is a” varArray(1) = “Variant array” varArray(2) = “stored in the” varArray(3) = “Session object” Session(“Variant_Array”) = varArray 3. 应用程序和会话在何时启动和结束 在介绍ASP应用程序和会话如何进行工作时提到过这个内容。以最基本的术语概述如下: · 当第一个用户请求应用程序作用域内(即Web网站的缺省根目录内),或者在该网站的一个子目录内的一个用户定义的虚拟应用程序的一个ASP网页时,启动该应用程序。在任何用户会话启动之前发生。 · 当任意用户第一次请求在缺省应用程序或一个虚拟应用程序内的一个ASP网页时,启动一个会话(如果还没一个活动的会话)。 · 当用户在会话指定的超时周期内没有下载一个ASP网页时,会话结束。超时时间可以在脚本代码中使用Session.Timeout属性进行设置,可在Properties对话框中对各个应用程序单独设置,或者通过Active Directory的IIS:部分修改IIS元数据库中的缺省值进行设置。调用Session.Abandon方法的一个网页完成执行以后,会话也会结束。 · 在一个应用程序中的最后一个活动会话结束以后,该应用程序立即结束。 4. ASP处理指令 正如在第1章中所看到的,可以把一条处理指令增加到一个ASP网页。处理指令可以根据需要包含一个以上的条目。可以在语句中使用的关键字及其说明如表3-10所示: 表3-10 ASP指令关键字及说明 指令关键字 说 明
3.3.4 活动的ASP Application对象 我们提供一些简单的网页,这些网页示范了使用过程中的ASP Application和Session对象。为了能够正常使用,必须把它们放到服务器上的一个虚拟应用程序内,并且把所提供的global.asa文件放到该应用程序的根目录中。最简单的办法是把global.asa文件放到缺省Web网站的根目录(缺省情况下是C:/InetPub/WWWRoot)中。 对任何已有的global.asa文件重命名是一个好办法,可以在以后对该文件进行恢复。 本书的所有例子文件都可以从我们的Web网站上得到,在例子的Chapter03子目录中还有本章的所有其余示例网页。 在Chapter03子目录中,Default.asp网页是一个简单的菜单,该菜单允许运行Application和Session示例网页,运行屏幕如图3-13所示: 1. 显示Application集合的内容 单击第一个链接,打开名为show_application.asp的Application对象示例页面。它显示了虚拟应用程序当前的Application对象的内容,如图3-14所示: 注意到ASPCounter对象是StaticObjects集合的一个成员(通过<OBJECT>元素进行定义),但是其余部份(由Server.CreateObject实例化)是Contents集合的成员。 可以看到使用global.asa例子网页放到这些集合中的值,这在前面已经看到: <!-- Declare instance of the ASPCounter component with application-level scope //--> <OBJECT ID=”ASPCounter” RUNAT=”Server” SCOPE=”Applicatoin” PROGID=”MSWC.Counters”> </OBJECT> ... ... <SCRIPT LANGUAGE=”VBScript” RUNAT=”Server”> Sub Application_onStart() ‘Create an instance of an ADO Connection with application-level scope Set Application(“ADOConnection”) = Server.CreateObject(“ADODB.Connection”) Dim varArray(3) ‘Create a Variant array and fill it varArray(0) = “This is a” varArray(1) = “Variant array” varArray(2) = “stored in the” varArray(3) = “Application object” Application(“Variant_Array”) = varArray ‘Store it in thd Application Application(“Start_Time”) = CStr(Now) ‘Store the date/time as a string Application(“Visit_Count”) = 0 ‘Set counter variable to zero End Sub ... ... </SCRIPT> (1) 遍历Contents集合的代码 为了遍历Contents集合,可使用一个For Each ... Next结构。集合中的每一项可以是一个简单的Variant类型变量、一个Variant数组或者一个对象的引用。因为需要对每种类型的值进行不同的处理,所以就不得不对每一个进行检查来判别其类型。 在VBScript中可使用VarType函数完成这个工作。这里使用IsObject和IsArray函数代替: For Each objItem in Application.Contents If IsObject(Application.Contents(objItem)) Then Response.Write “Object reference: ‘” & objItem & “' ” ElseIf IsArray(Application.Contents(objItem)) Then Response.Write “Array: ‘” & objItem & “' contents are: ” VarArray = Application.Contents(objItem) ‘Note: the following only works with a one-dimensional array For intLoop = 0 To UBound(varArray) Response.Write “ Index(“ & intLoop & “) = “ & _ VarArray(intLoop) & “ ” Next Else Response.Write “Variable: ‘” & objItem & “' = “ _ & Application.Contents(objItem) & “ ” End If Next 注意程序如何从Application对象检索该数组。将其分配给一个局部(Variant)变量,使用下面的语句: varArray = Application.Contents(objItem) 使用UBound函数可以查找出数组的大小(元素的数量),这个值可以作为遍历的终止条件: For intLoop = 0 UBound(varArray) 这个例子是一维数组,并将只显示这样的一个数组的内容。可根据需要编辑代码以处理多维数组,例如: For intLoop = 0 To UBound(varArray) IntNumberOfDimensions = UBound(varArray, 1) For intDimension = 0 To intNumberOfDimensions Response.Write “ Index(“ & intLoop & “) = “ _ & varArray(intLoop, intDimension) Next Response.Write “ ” Next (2) 遍历StaticObjects集合的代码 StaticObjects集合包含了所有在global.asa中使用<OBJECT>元素声明的对象引用。因为每个条目都是一个对象变量,可用简单些的代码对这个数组进行遍历。我们将输出对象的名字(在ID属性中原有的定义): For Each objItem in Application.StaticObjects If IsObject(Application.StaticObjects(objItem)) Then Response.Write “<OBJECT> element: ID='” & objItem & “' ” End If Next (1) 遍历Contents集合的代码 为了遍历Contents集合,可使用一个For Each ... Next结构。集合中的每一项可以是一个简单的Variant类型变量、一个Variant数组或者一个对象的引用。因为需要对每种类型的值进行不同的处理,所以就不得不对每一个进行检查来判别其类型。 在VBScript中可使用VarType函数完成这个工作。这里使用IsObject和IsArray函数代替: For Each objItem in Application.Contents If IsObject(Application.Contents(objItem)) Then Response.Write “Object reference: ‘” & objItem & “' ” ElseIf IsArray(Application.Contents(objItem)) Then Response.Write “Array: ‘” & objItem & “' contents are: ” VarArray = Application.Contents(objItem) ‘Note: the following only works with a one-dimensional array For intLoop = 0 To UBound(varArray) Response.Write “ Index(“ & intLoop & “) = “ & _ VarArray(intLoop) & “ ” Next Else Response.Write “Variable: ‘” & objItem & “' = “ _ & Application.Contents(objItem) & “ ” End If Next 注意程序如何从Application对象检索该数组。将其分配给一个局部(Variant)变量,使用下面的语句: varArray = Application.Contents(objItem) 使用UBound函数可以查找出数组的大小(元素的数量),这个值可以作为遍历的终止条件: For intLoop = 0 UBound(varArray) 这个例子是一维数组,并将只显示这样的一个数组的内容。可根据需要编辑代码以处理多维数组,例如: For intLoop = 0 To UBound(varArray) IntNumberOfDimensions = UBound(varArray, 1) For intDimension = 0 To intNumberOfDimensions Response.Write “ Index(“ & intLoop & “) = “ _ & varArray(intLoop, intDimension) Next Response.Write “ ” Next (2) 遍历StaticObjects集合的代码 StaticObjects集合包含了所有在global.asa中使用<OBJECT>元素声明的对象引用。因为每个条目都是一个对象变量,可用简单些的代码对这个数组进行遍历。我们将输出对象的名字(在ID属性中原有的定义): For Each objItem in Application.StaticObjects If IsObject(Application.StaticObjects(objItem)) Then Response.Write “<OBJECT> element: ID='” & objItem & “' ” End If Next 1. 增加值到Contents集合 增加值到Contents集合的方法,与在global.asa网页的脚本代码中使用过的方法相同。示例网页允许把一个新的Variant值增加到Application对象中,并已有建议的名字和值(可根据需要进行编辑),如图3-15所示: 单击按钮,重新载入这个网页,把值增加到Application.Contents集合中,并且在列表中显示,如图3-16所示: 增加新的Contents条目的代码 所有的按钮和其他HTML控件放置在示例网页中的一个窗体上。ACTION设置了当前网页的路径,提交该窗体时,重新装入。METHOD属性为“POST”,所以控件中的值出现在Request.Form集合中。在以前的章节中采用过这两种技术: <FORM ACTION=”<% = Request.ServerVariables(“SCRIPT_NAME”) %>” METHOD=”POST”> 该窗体上的按钮都是普通的HTML INPUT控件,具有相同的标题(三个空格)但名字不同。例如,创建第一个按钮(把值增加到Application对象中)的代码是: <INPUT TYPE=”SUBMIT” NAME=”cmdAdd” VALUE=” ”> 重新载入该网页时,检查Request.Form集合,判定单击的是哪个SUBMIT按钮,并进行相应的处理。如果是增加一个值到Application对象的按钮(该按钮在HTML的<INPUT>元素中被命名为cmdAdd),使用下面的程序段: If Len(Request.Form("cmdAdd")) Then strVarName = Request.Form("txtVarName") strVarValue = Request.Form("txtVarValue") Application.Lock Application("strVarName") = strVarValue Application.Unlock End If 注意程序如何使用Application.Lock和Application.Unlock方法,确保这些值不会因两个用户并发地访问而产生混乱。如果只是对一个特定的值进行设置,一般不可能发生这种情况。但一直使用Lock和Unlock方法是明智的。 2. Contents集合中删除值 在例子网页的底部有两个按钮,如图3-17所示: 这两个按钮允许从Application.Contents集合中删除值。第一个按钮从集合中删除单个的指定值,下拉列表框显示的是Contents集合值的名字的列表(记住,不能从StaticObjects集合中删除值,因为它们是静态的)。 通过遍历Contents集合(如前面我们所做的)执行ASP网页时,创建该列表。但是,我们仅收集每项的名字并把它们放到<SELECT>列表元素内的<OPTION>元素中: … <SELECT NAME=”lstRemove” SIZE=”1”> <% For Each objItem in Application.Contents Response.Write “<OPTION>” & objItem & “</OPTION>” Next &> </SELECT> … 该ASP代码执行以后,在浏览器中看到的结果是: <SELECT NAME=”lstRemove” SIZE=”1”> <OPTION>ADOConnection</OPTION> <OPTION>Variant_Array</OPTION> <OPTION>Start_Time</OPTION> <OPTION>Visit_Count</OPTION> <OPTION>My_New_Value</OPTION> </SELECT> (1) 删除单个值 当单击按钮删除单个值时,该窗体再次提交给相同的网页,但是这一次将查找名为cmdRemoveThis的SUBMIT按钮。如果存在(即单击了这个按钮),则使用列表框的值,调用Application.Contents集合的Remove方法: If Len(Request.Form("cmdRemoveThis")) Then strToRemove = Request.Form("lstRemove") Response.Write "strToRemove = " & strToRemove Application.Lock Application.Contents.Remove(strToRemove) Application.Unlock End If 注意这是Contents集合的一个方法,而不是Application对象的。语法是Application.Contents.Remove,而不是Application.Remove。 从Contents集合中删除Start_Time值的结果如图3-18所示: (2) 删除所有的值 如果单击三个SUBMIT类型按钮中的最后一个(如图3-18所示),该网页中的代码将检测到单击的按钮为cmdRemoveAll,将执行Application.Contents集合的RemoveAll方法: If Len(Request.Form("cmdRemoveAll")) Then Application.Lock Application.Contents.RemoveAll Application.Unlock End If 再次提醒,这是Contents集合的一个方法,而不是Application。语法是Application.Contents.RemoveAll,而不是Application.RemoveAll。 图3-19所示的是从Contents集合中删除所有值的结果(记住在运行时间不能从StaticObjects集合删除项):
3.3.5 活动中的ASP Session对象 示例网页的第二个示例页面show_session.asp,示范了如何使用Session对象。可在Chapter03子目录中的开始菜单(Default.asp)中打开它。 1. 显示和更新Session集合 Session对象示例页面看起来与刚刚使用过的Application对象示例页面相似。它遍历Session对象的Contents和StaticObjects集合,显示其名字和(可能的话)相应的值。如果把这些值与Application对象页面进行比较,将会看到不同之处。 这里还能够看到客户端IP地址的一些其他值。这是当会话启动时global.asa中的代码从Request.ServerVariables集合中得到的。这个页面还显示四个会话属性的值,如图3-20所示: 下面是例子中使用的golbal.asa文件的相关段落,它把缺省值增加到图3-20所示的屏幕上所看到的会话中: ... <!-- Declare instance of the ASPContentLink component with session-level scope //--> <OBJECT ID="ASPContentLink" RUNAT="Server" SCOPE="Session" PROGID="MSWC.NextLink"> </OBJECT>
<SCRIPT LANGUAGE="VBScript" RUNAT="Server"> ... ... Sub Session_onStart()
'Create an instance of the AdRotator component with session-level scope Set Session("ASPAdRotator") = Server.CreateObject("MSWC.AdRotator") Dim varArray(3) 'Create a Variant array and fill it varArray(0) = "This is a" varArray(1) = "Variant array" varArray(2) = "stored in the" varArray(3) = "Session object" Session("Variant_Array") = varArray 'Store it in the Session Session("Start_Time") = CStr(Now) 'Store the date/time as a string
'We can access the contents of the Request and Response in a Session_onStart 'event handler for the page that initiated the session. This is the *only* 'place that the ASP page context is available like this. 'as an example, we can get the IP address of the user: Session("Your_IP_Address") = Request.ServerVariables("REMOTE_ADDR")
Application.Lock 'Prevent concurrent updates intVisits = Application("Visit_Count") + 1 'Increment counter variable Application("Visit_Count") = intVisits 'Store back in Application Application.Unlock 'Release lock on Application
End Sub ... ... </SCRIPT> 遍历Contents和StaticObjects集合的代码与前面在Application对象示例中使用的代码一样,只不过这里引用了Session.Contents和Session.StaticObjects集合,而不是Application.Contents和Appliction.StaticObjects集合。 靠近页面底部的按钮的功能是把值增加到Session.Contents集合和从Session.Contents集合删除值。这些按钮与在Application对象示例页面中相应的按钮工作方式相同,这里访问的是Session.Contents集合,以及相应的Remove和RemoveAll方法。我们不再重复解释。 2. 终止一个用户会话 在Session对象页面的底部有一个按钮,该按钮终止当前的用户会话,这通过调用Session对象的Abandon方法实现。它与其余的HTML控件在相同的窗体上,名为cmdAbandon。当该窗体再次被提交给这个网页时,在Request.Form集合中查找这个值(如同在Application对象例子中做的一样)。如发现这个值,则将该用户重定向到另一个网页: If Len(Request.Form("cmdAbandon")) Then Response.Clear Response.Redirect "abandon.asp" Response.End End If 新的页面名为abandon.asp,十分简单,除了创建消息的文本和HTML以外只有如下的代码: <% Session.Abandon %> 该网页的其余部分只是一个包含单个SUBMIT按钮的窗体。注意如何使用来自Request.ServerVariables集合的引用网页(HTTP_REFERER)的URL的值,以确保重新载入前一个网页(Session对象例子网页): <FORM ACTION="<% = Request.ServerVariables("HTTP_REFERER") %>" METHOD="POST">
<DIV CLASS="subhead">Your Session Has Been Terminated</DIV> A new <B>Session</B> will be started when you load another
ASP page. It will contain any values that are defined in
the <B>global.asa</B> file for this application.
<INPUT TYPE="SUBMIT" NAME="cmdOK" VALUE=" "> Return to the previous page