3.4 Web窗体的事件驱动编程

ASP.NET基于Web Form的事件驱动交互模型使Web程序设计技术向前迈进了一大步,因此,用户可以用熟悉的C/S模式的Windows应用程序设计方法,采用面向对象的技术来实现Web事件B/S模式的应用程序。尽管事件驱动实现的底层机制不同,但是它们提供的面向对象的事件驱动编程接口是相似的,下面就来介绍Web窗体的事件驱动编程。

3.4.1 事件驱动编程的概念

在DOS时代的编程模型中,程序是按顺序执行的,即使调用了函数和过程,也不会对执行顺序有多少改变,这类程序称为面向过程的程序设计。面向过程的应用程序一般有一个明显的开始,一个明显的过程和一个明显的结束。

基于Windows的应用程序设计方法与DOS程序设计的方法有很大的不同,关键不同在于Windows应用程序是事件驱动的。事件驱动编程改变了过程驱动这一观念,程序不再以顺序执行的方式来完成任务,而是通过用户与程序的交互来决定执行的顺序,这种程序设计称为事件驱动编程。

事件驱动程序的最大特点就是,程序的执行不是由程序的顺序来控制的,而是由事件的发生顺序来控制的。例如,Windows操作系统本身就是事件驱动的,Windows启动后就等待事件的发生,只要发生了事件,操作系统就会执行相应的事件处理程序,如用户右击桌面就会弹出一个快捷菜单,然后等待用户的下一个操作。同样,一个查询学生成绩、打印学生成绩的程序,基于过程的程序设计逻辑流程图如图3.11所示,基于事件的程序设计逻辑流程图如图3.12所示。

图3.11 基于过程的程序设计逻辑流程图

图3.12 基于事件的程序设计逻辑流程图

这种基于过程驱动的程序,只能让用户按照程序规定好的步骤进行操作,用户不能以任何顺序跳跃性地输入数据和使用功能。基于事件驱动的程序,是围绕着消息的产生与处理而展开的,而消息是不会以任何预定的顺序出现的,因此,Windows程序设计主要是编写消息的接收与发送的响应代码。按照图3.12所设计的程序,用户可以先打印学生成绩,也可以先查询学生成绩,这种事件驱动的方式提供了许多的便利,这在交互性操作应用程序设计上显示了极大的优越性。

现在的ASP.NET程序设计也实现了事件驱动,用户在浏览器中输入页面的URL地址,页面就会显示在浏览器中,等待用户的操作。在用户单击页面或者输入数据时,页面及控件的值和状态就会传回服务器,ASP.NET服务器就会执行一些代码,进行响应,在代码执行完成后,页面就会返回浏览器,等待用户的下一个操作。

Web窗体实现事件驱动的编程模型和Windows窗体中实现事件驱动编程模型的机理是不一样的。C/S结构的Windows窗体应用程序与服务器之间实现的是有状态持续连接,用户的交互操作、输入的数据和应用程序状态都是连续地与服务器通信,服务器始终知道当前连接的应用程序状态。而Web应用程序与服务器之间是通过HTTP协议来实现通信的,是无状态的断续连接。具体来讲,用户在浏览器中输入一个.aspx页面并发出请求,服务器收到这个请求后运行这个.aspx文件并产生一个页面,然后将该页面传回给客户端的浏览器并显示给用户,此后服务器就会断开连接,准备为其他请求提供服务。也就是说,ASP.NET页面不会永久地保存在服务器的内存中,当发生其他请求时,上一次的页面就会从内存中消失。那么,ASP.NET的Web窗体是如何实现有状态的执行环境的呢?

ASP.NET的Web窗体提供了一种标准的保持状态的方式,并隐藏了实际执行的细节。其原理是,Web页面会在两次请求之间存储自己的ViewState(视图状态),ViewState保存了页面及页面上所有控件的状态值。ViewState由System.Web.UI.StateBag对象负责存储。在服务器端,将ViewState存储为一个字符串变量,返回客户端;在客户端,将ViewState存储为一个隐藏的窗体字段。用户调试例3.1中的页面,在打开页面的浏览器中,通过执行“查看”→“源文件”命令,就会看到如下代码:

              <!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>
                      无标题页
              </title></head>
              <body>
                  <form name="form1" method="post" action="Default.aspx" id="form1">
              <div>
              <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value=
              "/wEPDwUKLTY3Mjk5NzA2N2Rkf9VsbCULVRjrqThOhmm+sCk2ess=" />
              </div>
              <div>
              <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION"
              value="/wEWAwKknMnbBALs0bLrBgKSotaIC7n4RAtWMYeo8T/rZsVHB17Rs3GI" />
              </div>
                  <div>
                          <span id="lbl1"></span>
                          <br />
                          <span id="lbl2">请输入您的姓名:</span>
                          <input name="TextBox1" type="text" id="TextBox1" style=
              "height:23px;" />
                          <br />
               <input type="submit" name="btn1" value="请按我一下" id="btn1"
              style="height:25px;width:104px;text-align: left" />
                  </div>
                  </form>
              </body>
              </html>

上面源文件中加粗部分代码是一个隐藏的窗体字段,存储了服务器控件的值,Web窗体处理程序可以读取这些值并恢复控件的状态。由此,可以看到页面的状态与页面是一起存储的,并随页面一起传送状态,而不是存储在服务器中的。

Web窗体和服务器控件都默认支持ViewState,可以通过<% @page %>指令实现对页面级的ViewState状态的打开(True)或关闭(false),代码如下:

              <% @page EnableViewState="false" %>

在单个控件上控制ViewState状态,可以通过添加EnableViewState属性来控制,代码如下:

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

3.4.2 Web窗体的处理过程及窗体事件

要学习Web窗体的事件驱动编程,必须要掌握Web窗体的处理过程及各阶段发生的事件。读者已经知道,Web Form的扩展名是.aspx。当一个浏览器第一次请求一个aspx文件时,Web窗体将按照3.3节的执行过程被CLR编译器编译成一个Page类并实例化,当每次请求这个页面时,这个动态创建的Page类就会实例化,从而得到一个可输出HTML页面的Page对象。这样,ASP.NET就做到了一次编译多次执行,进而极大地提高了Web服务速度。

一个Web窗体页面(Page对象)从实例化分配内存空间到处理结束释放内存,一般要经历10个阶段,即页面初始化、视图状态加载、回传数据处理、页面加载、回传数据变化检查、回传事件处理、页面预返回、保存视图状态、页面返回、页面卸载,其中有一些阶段会触发Page对象的事件,这些事件各自有不同的事件处理程序。

(1)页面初始化。此阶段是初始化页面生命周期内所需的设置,并生成控件树。初始化会触发Page对象的第一个事件Page_Init,用户可以利用这个事件处理过程重置控件的属性。但需要注意的是,Page_Init事件只是在第一次调入页面时被调用,重新载入页面时并不触发该事件。实际应用时,一般都跳过Page_Init事件,直接使用Page_OnLoad事件。

(2)视图状态加载。在此阶段读取隐藏窗体字段的值,恢复控件的ViewState属性,也就是页面状态。在此阶段没有相关联的用户事件。

(3)回传数据处理。页面加载在Request对象中缓存的窗体数据,然后更新页面和控件属性。Request对象中缓存的窗体数据是由于用户在客户端操作复选框等控件而回传的数据,在这个阶段窗体和控件被更新,以反映由用户操作而引起的客户变化。在此阶段没有相关联的用户事件。

(4)页面加载。在此阶段创建控件树中的服务器控件,初始化这些控件并恢复状态,会触发Page对象的Load事件。在这个事件处理过程中,用户可以根据Page.IsPostBack属性检查页面是不是第一次被处理,在第一次处理页面时执行数据捆绑,或者在以后的循环过程中重新判断数据捆绑表达式。

(5)回传数据变化检查。在此阶段检查当前回传和前一次回传之间的状态改变,并发送通知,引发更改事件(RaisePostDataChangeEvent)。

(6)回传事件处理。执行与导致回传的客户端事件相关联的.aspx服务器端代码。回传事件是由客户端动作导致的页面请求,例如,单击一个按钮,页面回传,在此阶段就执行与单击“提交”事件相关联的服务端OnClick事件代码。第(5)、(6)阶段是ASP.NET事件驱动模型的核心阶段。

(7)页面预返回。在页面输出之前,执行任意更新处理。与这个阶段相关联的Page对象事件是PreRender事件。

(8)保存视图状态。在此阶段,页面将ViewState属性的内容序列化为一个字符串,这个字符串将作为一个隐藏域被附加到HTML页面。

(9)页面返回。在此阶段创建呈现在客户端的HTML输出。

(10)页面卸载。这个阶段发生于一个窗体完成了它的任务并且准备卸载的时候,这时引发页面的Unload事件。Unload事件完成最后的资源清理工作,如关闭文件、关闭数据库连接、丢弃对象等。

提示:掌握Web窗体处理过程的10个阶段是动态网站编程的高级论题,用户可以先了解这个10个阶段,重点掌握页面的Page_Load、Page_PreRender和Page_Unload事件的触发时间和能够完成的任务。

当页面被请求时,Web窗体包含的类和服务器控件负责执行请求,然后将HTML返回到客户端。如前所述,由于HTTP协议的原因,客户端与服务器的通信是无状态、无连接的。ASP.NET提供了一种内建的机制,以一种透明的方式(ViewState)保存和恢复页面的状态。在这种内建机制中页面总是将状态传给自己,并来回携带这种状态。通过这种方式,使客户端的操作成为一个连续的处理过程。

回传事件是由客户端动作导致的页面请求。通常来讲,当用户单击“提交”按钮时,就会发生回传事件,也就是HTML页面就会发送请求到同一个aspx页面,在服务器上,负责处理请求的代码就会获得一个Page类的实例,然后经历Web窗体的10个阶段。上述页面处理的10个阶段可以简化为三步,即页面恢复、处理回传事件和页面呈现。

作为页面处理的第一步,HTTP运行库首先构建Web窗体的控件树,设置IsPostBack属性,如果页面正在被处理,则此属性的值是true,然后,恢复视图状态,更新各个控件状态,反映客户端的动作,接下来执行OnLoad事件。第二步,处理回传事件,回传事件的目的就是执行服务器端的代码,来响应用户的输入,那么服务器是如何知道要执行什么方法呢?这主要是通过HTML回传被单击控件的ID和控件的runat="server"属性,以及为此控件编写的服务器端代码来实现回传事件处理的。第三步,页面呈现,执行完服务器端代码后,页面开始其呈现阶段,页面首先触发PreRender事件,然后保存视图状态,呈现HTML输出,此后开始进入下一次循环。