asp.net夜话之五:Page类和回调技术
        在今天我主要要介绍的有如下知识点:
Page类介绍
Page的生命周期
IsPostBack属性
ClientScriptManager类
回调技术(CallBack)
        Page类介绍

        asp.net有时候也被成为WebForm,因为开发一个asp.net页面就像开发一个WinFrom窗体一样,我们同样可以采用拖拽控件、双击产生相关处理代码的方法。在asp.net中,创建一个页面可以采用两种模型。

单页模型
        用Dreamweaver创建的asp.net页面就是单页模型,当然利用Visual Studio 2005也能创建单页模型,不过在Visual Studio 2005中创建的页面默认不是单页模型,要想在Visual Studio 2005创建单页模型的网页如下:

        注意确保“将代码放在单独的文件中”选项处于未选中状态,默认情况下这个选项是处于选中状态的。这样就创建了单页模型的网页。
此时的页面代码如下:
     1. <%@ Page Language="C#" %>
     2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     3. <script runat="server">
     4. </script>
     5. <html xmlns="http://www.w3.org/1999/xhtml" >
     6. <head runat="server">
     7.         <title>无标题页</title>
     8. </head>
     9. <body>
    10.         <form id="form1" runat="server">
    11.         <div>
    12.            
    13.         </div>
    14.         </form>
    15. </body>
    16. </html>

 
        注意在页面中有这样一句代码:
     1. <script runat="server">
     2. </script>
        这句代码与普通javascript语句块不同的是有一个runat="server"属性,表示这里的代码是在服务器上运行的C#代码。切换到设计视图,然后双击页面,然后这部分会变成如下的样子:
     1. <script runat="server">
     2.         protected void Page_Load(object sender, EventArgs e)
     3.         {
     4.         }
     5. </script>
        其中Page_Load就是页面加载的时候在服务器上运行的方法。
        单页模型的特点是HTML标记、控件代码及服务器端运行的C#代码全部包含在一个aspx页面中,Web服务器第一次运行该页面的时候会将这个页面生成一个类文件,对于上面的Index.aspx页面,会生成 ASP.Index_aspx的类,然后再将这个ASP.Index_aspx类编译成IL代码,Web服务器通过CLR(Common Language Runtime,通用语言运行环境)运行相应的IL代码。
        单页模型的缺点是页面和代码混在一起,维护起来较为麻烦。
代码页面分离模式
        代码页面模式就是将页的标记(HTML代码)和服务器端元素放在.aspx页面中,而也代码在位于一个.aspx.cs中。采用默认方式创建的aspx网页就是这种方式。
        下面就是一个采用代码页面分离模式创建的Home.aspx页面的代码:
     1. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Home.aspx.cs" Inherits="Home" %>
     2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     3. <html xmlns="http://www.w3.org/1999/xhtml" >
     4. <head runat="server">
     5.         <title>无标题页</title>
     6. </head>
     7. <body>
     8.         <form id="form1" runat="server">
     9.         <div>
    10.            
    11.         </div>
    12.         </form>
    13. </body>
    14. </html>

 
        其对应的页代码是:
     1. using System;
     2. using System.Data;
     3. using System.Configuration;
     4. using System.Collections;
     5. using System.Web;
     6. using System.Web.Security;
     7. using System.Web.UI;
     8. using System.Web.UI.WebControls;
     9. using System.Web.UI.WebControls.WebParts;
    10. using System.Web.UI.HtmlControls;
    11. public partial class Home : System.Web.UI.Page
    12. {
void Page_Load() void Page_Load(object sender, EventArgs e)
    14.         {
    15.         }
    16. }
 
        首先要关注的aspx的头部分代码:
   1. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Home.aspx.cs" Inherits="Home" %>
        @Page是一个页面指令,在这里Language="C#"指明了当前页面采用的后台代码是C#语言,CodeFile="Home.aspx.cs"表示这个页面对应的页代码文件是Home.aspx.cs这个文件,Inherits="Home" 表示当前aspx页继承自Home这个类。
现在再关注一下页代码文件声明:
        1. public partial class Home : System.Web.UI.Page
从这部分代码可以看出Home类是继承自System.Web.UI.Page类的。注意这里还有一个C#2.0的关键字partial,这个关键字表示当前代码是一个局部类,以表示这个类是构成整个Web页面窗体的一部分。Web服务器运行这个页面的时候最终会将aspx页面和对应的页代码编译成一个类文件,然后生成IL代码。
代码页面分离模式的好处是页面展示部分和逻辑控制部分的代码分离开来,便于管理和维护,这也是微软推荐的开发方式。
asp.net页面的声明周期
        asp.net页面运行的时候将经历一个声明周期,这个生命周期中会进行一系列的操作,调用一系列的方法。了解asp.net页面的生命周期对于精确控制页面的控件呈现方式和行为非常重要。
        一般说来一个常规页面要经历如下几个生命周期阶段:
阶段  说明
        页请求  页请求发生在页生命周期开始之前。用户请求页时,ASP.NET 将确定是否需要分析和编译页(从而开始页的生命周期),或者是否可以在不运行页的情况下发送页的缓存版本以进行响应。
        开始  在开始阶段,将设置页属性,如 Request 和 Response。在此阶段,页还将确定请求是回发请求还是新请求,并设置 IsPostBack 属性。此外,在开始阶段期间,还将设置页的 UICulture 属性。
        页初始化  页初始化期间,可以使用页中的控件,并将设置每个控件的 UniqueID 属性。此外,任何主题都将应用于页。如果当前请求是回发请求,则回发数据尚未加载,并且控件属性值尚未还原为视图状态中的值。
        加载  加载期间,如果当前请求是回发请求,则将使用从视图状态和控件状态恢复的信息加载控件属性。
        验证  在验证期间,将调用所有验证程序控件的 Validate 方法,此方法将设置各个验证程序控件和页的 IsValid 属性。
        回发事件处理  如果请求是回发请求,则将调用所有事件处理程序。
        呈现  在呈现期间,视图状态将被保存到页,然后页将调用每个控件,以将其呈现的输出提供给页的 Response 属性的 OutputStream。
        卸载  完全呈现页、将页发送至客户端并准备丢弃时,将调用卸载。此时,将卸载页属性(如 Response 和 Request)并执行清理。
        在页的生命周期中,一般会有如下事件:

页事件  典型使用
Page_PreInit  使用 IsPostBack 属性确定是否是第一次处理该页。
创建或重新创建动态控件。
动态设置主控页。
动态设置 Theme 属性。
读取或设置配置文件属性值。
注意:如果请求是回发请求,则控件的值尚未从视图状态还原。如果在此阶段设置控件属性,则其值可能会在下一阶段被改写。
Page_Init  读取或初始化控件属性。
Page_Load  读取和更新控件属性。
Control events  执行特定于应用程序的处理:
如果页包含验证程序控件,请在执行任何处理之前检查页和各个验证控件的 IsValid 属性。
处理特定事件,如 Button 控件的 Click 事件。
Page_PreRender  对页的内容进行最后更改。
Page_Unload  执行最后的清理工作,可能包括:
关闭打开的文件和数据库连接。
完成日志记录或其他特定于请求的任务。
        需要注意的是,每个asp.net控件也有与asp.net类似的生命周期,如果aspx页面中包含有asp.net服务器控件,那么在调用页面的方法时也会调用控件的相关方法。
另外,Web应用程序是无状态的。每次请求一个新网页或者刷新页面服务器都会创建一个当前页的新实例,这就意味着无法获取页面的以前的信息,如果确实需要这么做,需要采用额外的机制。
        我们将刚才新建的Index.aspx页面中添加代码,如下:
     1. <%@ Page Language="C#" %>
     2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     3. <script runat="server">
     4.         string date;
     5.         protected void Page_Load(object sender, EventArgs e)
     6.         {
     7.                 if (date == null)//如果date为空则设置为当前时间的字符串形式
     8.                 {
     9.                         date = DateTime.Now.ToString();
    10.                 }
    11.                 Response.Write("当前时间:"+date);
    12.         }
    13. </script>
    14. <html xmlns="http://www.w3.org/1999/xhtml" >
    15. <head runat="server">
    16.         <title>无标题页</title>
    17. </head>
    18. <body>
    19.         <form id="form1" runat="server">
    20.         <div>
    21.            
    22.         </div>
    23.         </form>
    24. </body>
    25. </html>
        按照正常理解,第一次运行的时候date字符串为null,会被设置成系统当前的字符串表示形式,并且输出,再次刷新的时候date字符串不再为空,会依然输出刚才的时间字符串,但是结果却不是这样。第一次运行的结果:
 
        刷新页面之后的结果:
 
        这就证明了即使是刷新当前页也会重新生成一个当前页面的实例,因为只有在生成页面新实例的情况下date字符串变量才为空,才会被重新设置值。
IsPostBack属性
        Page类有一个IsPostBack属性,这个属性用来指示当前页面是第一次加载还是响应了页面上某个控件的服务器事件导致回发而加载。
这次我们继续对Index.aspx页面添加代码,在页面中增加了一个Button控件,如下:
     1. <%@ Page Language="C#" %>
     2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     3. <script runat="server">
     4.         string date;
     5.         protected void Page_Load(object sender, EventArgs e)
     6.         {
     7.                 if (date == null)//如果date为空则设置为当前时间的字符串形式
     8.                 {
     9.                         date = DateTime.Now.ToString();
    10.                 }
    11.                 Response.Write("当前时间:"+date);
    12.                 if (!Page.IsPostBack)
    13.                 {
    14.                         Response.Write("第一次加载。");
    15.                 }
    16.                 else
    17.                 {
    18.                         Response.Write("响应客户端回发而加载。");
    19.                 }
    20.         }
    21.         protected void btnOK_Click(object sender, EventArgs e)
    22.         {
    23.         }
    24. </script>
    25. <html xmlns="http://www.w3.org/1999/xhtml" >
    26. <head runat="server">
    27.         <title>无标题页</title>
    28. </head>
    29. <body>
    30.         <form id="form1" runat="server">
    31.         <div>
    32.                 <asp:Button ID="btnOK" runat="server" OnClick="btnOK_Click" Text="提交" /></div>
    33.         </form>
    34. </body>
    35. </html>
        页面第一次运行的结果:
 
        按一下F5刷新页面的结果:
 
        点击一下“提交”按钮之后的结果:
 
        由此可见每次打开一个页面和刷新一个页面效果都是一样的,只有响应客户端回发时IsPostBack属性才是true。了解这个属性和服务器采用了一种机制来“记录”服务器控件的状态这种做法(其实利用了ViewState和ControlState机制,这部分后续文章中会讲到)对于将来数据绑定会有很大作用。
动态输出javascript脚本
        对于Index.aspx页面上面的执行情况,我们看到了满意的结果。我们再来看一下这个页面在客户端生成的HTML代码,在浏览器窗口打开的页面点鼠标右键,然后选择“查看源文件”,HTML代码如下:
     1. 当前时间:2008-9-21 0:04:33响应客户端回发而加载。
     2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     3. <html xmlns="http://www.w3.org/1999/xhtml" >
     4. <head><title>
     5.    无标题页
     6. </title></head>
     7. <body>
     8.         <form name="form1" method="post" action="Index.aspx" id="form1">
     9. <div>
    10. <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTY3NzE5MjIyMGRkD2VvJFADDdEHh4W9UfAyzIvI3ss=" />
    11. </div>
    12.         <div>
    13.                 <input type="submit" name="btnOK" value="提交" id="btnOK" /></div>
    14.            
    15. <div>
    16.    <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAgL8vZamCQLdkpmPAeM33vfm1ARVNKdKAoq5+eQdFI1J" />
    17. </div></form>
    18. </body>
    19. </html>
        我们会看到“当前时间:2008-9-21 0:04:33响应客户端回发而加载。”这句话位于<html></html>标记之外。在第一夜时候就提到过,asp.net 页面是满足XML标准的HTML语言,但是通过在Page_Load事件中利用Response属性会将文字输出在<html></html>标记之外,不符合XHTML标准。这对于普通页面来说也许并无大碍,但是如果在频繁输出 javascript脚本的网页中,可能会对网页的客户端执行效果产生影响。因为javascript脚本块在客户端调用方法之前还是客户端调用方法之后效果可能会不一样。
        下面在Home窗体的Page_Load事件中添加代码,如下:
     1. using System;
     2. using System.Data;
     3. using System.Configuration;
     4. using System.Collections;
     5. using System.Web;
     6. using System.Web.Security;
     7. using System.Web.UI;
     8. using System.Web.UI.WebControls;
     9. using System.Web.UI.WebControls.WebParts;
    10. using System.Web.UI.HtmlControls;
    11. public partial class Home : System.Web.UI.Page
    12. {
    13.         protected void Page_Load(object sender, EventArgs e)
    14.         {
    15.                 if (!Page.IsPostBack)
    16.                 {
    17.                         Response.Write("<script language='javascript'>alert('" + DateTime.Now.ToString() + "')</script>");
    18.                 }
    19.         }
    20. }

 
        这样每次运行Home.aspx页面的时候都会弹出一个对话框,如下图:
        这不是我们所关心的,我们关注的是生成的HTML代码,如下:
     1. <script language='javascript'>alert('2008-9-21 0:22:52')</script>
     2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     3. <html xmlns="http://www.w3.org/1999/xhtml" >
     4. <head><title>
     5.    无标题页
     6. </title></head>
     7. <body>
     8.         <form name="form1" method="post" action="Home.aspx" id="form1">
     9. <div>
    10. <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJNzgzNDMwNTMzZGTB6tgIyCoS2q3pZeKmhFwC24pQzw==" />
    11. </div>
    12.         <div>
    13.            
    14.         </div>
    15.         </form>
    16. </body>
    17. </html>
 
        可以看见输出的javascript代码在<html></html>标记之外。
        在Page类中有一个ClientScript属性,它是ClientScriptManager的实例,这个类是在asp.net2.0中新增的。ClientScriptManager有如下几个常用方法:
RegisterClientScriptBlock方法:向 Page 对象注册客户端脚本。
RegisterStartupScript方法:向 Page 对象注册启动脚本。
ClientScriptManager类通过键string和Type来唯一标识脚本。具有相同类型的键和Type的脚本识为同一脚本。
        下面对Home窗体的Page_Load事件中输入如下代码:
     1. using System;
     2. using System.Data;
     3. using System.Configuration;
     4. using System.Collections;
     5. using System.Web;
     6. using System.Web.Security;
     7. using System.Web.UI;
     8. using System.Web.UI.WebControls;
     9. using System.Web.UI.WebControls.WebParts;
    10. using System.Web.UI.HtmlControls;
    11. public partial class Home : System.Web.UI.Page
    12. {
void Page_Load() void Page_Load(object sender, EventArgs e)
    14.         {
    15.                 if (!ClientScript.IsClientScriptBlockRegistered(this.GetType(), "ClientScriptBlock"))
    16.                 {
    17.                         ClientScript.RegisterClientScriptBlock(this.GetType(), "ClientScriptBlock", "<script language='javascript'>alert('ClientScriptBlock')</script>");
    18.                 }
    19.                 if (!ClientScript.IsStartupScriptRegistered(this.GetType(), "StartupScript"))
    20.                 {
    21.                         ClientScript.RegisterStartupScript(this.GetType(), "StartupScript", "<script language='javascript'>alert('StartupScript')</script>");
    22.                 }
    23.                 //Response.Write("<script language='javascript'>alert('" + DateTime.Now.ToString() + "')</script>");
    24.         }
    25. }

 
        执行该页面时,会弹出两个提示窗口,生成的HTML代码如下:
     1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2. <html xmlns="http://www.w3.org/1999/xhtml" >
     3. <head><title>
     4.    无标题页
     5. </title></head>
     6. <body>
     7.         <form name="form1" method="post" action="Home.aspx" id="form1">
     8. <div>
     9. <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJNzgzNDMwNTMzZGTB6tgIyCoS2q3pZeKmhFwC24pQzw==" />
    10. </div>
    11. <script language='javascript'>alert('ClientScriptBlock')</script>
    12.         <div>
    13.                    </div>
    14.            
    15. <script language='javascript'>alert('StartupScript')</script></form>
    16. </body>
    17. </html>
        可以看出上面的两个方法输出的javascript脚本都在<form></form>标记之内,不会破环文章的结构,而且RegisterClientScriptBlock方法输出的javascript脚本代码块靠近<form>标记的开始标记,而 RegisterStartupScript方法输出的javascript脚本代码块靠近<form>标记的结束标记,了解这一点对于控制动态添加的客户端脚本的时间是非常有利的。
回调技术(CallBack)
        在asp.net中客户端与服务器端的交互默认都是整页面提交,此时客户端将当前页面表单中的数据(包括一些自动生成的隐藏域)都提交到服务器端,服务器重新实例化一个当前页面类的实例响应这个请求,然后将整个页面的内容重新发送到客户端,这种处理方式对运行结果没什么影响,不过这种方式加重了网络的数据传输负担、加大了服务器的工作压力,并且用户还需要等待最终处理结果。假如是我们希望有这么一个功能,当用户填写完用户名之后就检查服务器数据库里是否已存在该用户名,如果存在就给出已经存在此用户名的提示,如果不存在就提示用户此用户名可用,对于这种情况其实只需要传递一个用户名作为参数即可,上面的做法却需要提交整个表单,有点小题大做。解决上面的问题的办法目前主流做法有三种:纯javascript实现、微软Ajax类库实现还有用AjaxPro实现。后两种做法在稍后的文章中会讲到,这里我讲另外一种实现:通过回调技术。

        创建实现回调技术的网页与普通asp.net网页类似,只不过还需要做以下特殊工作:
        (1)让当前页面实现 ICallbackEventHandler接口,这个接口定义了两个方法:string GetCallbackResult ()方法和void RaiseCallbackEvent (string eventArgument)方法。其中GetCallbackResult ()方法的作用是返回以控件为目标的回调事件的结果,RaiseCallbackEvent()方法的作用是处理以控件为目标的回调事件。
        (2) 为当前页提供三个javascript客户端脚本函数。一个javascript函数用于执行对服务器的实际请求,在这个函数中可以提供一个字符串类型的参数发送到服务器端;另一个javascript函数用于接收服务器端方法的执行后返回的字符串类型结果,并处理这个结果;还有一个是执行对服务器请求的帮助函数,在服务器代码中通过GetCallbackEventReference()方法获取这个方法的引用时由asp.net自动生成这个函数。
        下面我以一个详细的例子来讲述如何使用回调,用Dreamweaver创建一个Register. aspx页面,代码如下:

 
     1. <%@ Page Language="C#" ContentType="text/html" ResponseEncoding="gb2312" %>
     2. <%@ Implements Interface="System.Web.UI.ICallbackEventHandler" %>
     3. <%@ Import Namespace="System.Text" %>
     4. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     5. <html xmlns="http://www.w3.org/1999/xhtml">
     6. <head>
     7. <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
     8. <title>用户注册</title>
     9. <script language="javascript">
    10. //客户端执行的方法
    11. //下面的方法是接收并处理服务器方法执行的返回结果
    12. function Success(args, context)
    13. {
    14.         message.innerText    = args;
    15. }
    16. //下面的方式是当接收服务器方法处理的结果发生异常时调用的方法
    17. function Error(args, context)
    18. {
    19.         message.innerText    = '发生了异常';
    20. }
    21. </script>
    22. <script language="c#" runat="server">
    23. string result="";
    24. // 定义在服务器端运行的回调方法.
    25. public void RaiseCallbackEvent(String eventArgument)
    26. {
    27.    if(eventArgument.ToLower().IndexOf("admin")!=-1)
    28.    {
    29.     result=eventArgument+"不能作为用户名注册。";
    30.    }
    31.    else
    32.    {
    33.     result=eventArgument+"可以注册。";
    34.    }
    35.    //throw new Exception();
    36. }
    37. //定义返回回调方法执行结果的方法
    38. public string GetCallbackResult()
    39. {
    40.    return result;
    41. }
    42. //服务器上执行的方法
    43. public void Page_Load(Object sender,EventArgs e)
    44. {
    45.    // 获取当前页的ClientScriptManager的引用
    46.         ClientScriptManager csm = Page.ClientScript;
    47.    // 获取回调引用。会在客户端生成WebForm_DoCallback方法,调用它来达到异步调用。这个方式是微软写的方法,会被发送到客户端
    48.    //注意这里的"Success"和"Error"两个字符串分别客户端代码中定义的两个javascript函数
    49.    //下面的方法最后一个参数的意义:true表示执行异步回调,false表示执行同步回调
    50.    String reference = csm.GetCallbackEventReference(this, "args","Success","","Error",false);
    51.    String callbackScript = "function CallServerMethod(args, context) {\n" +    
    52.     reference + ";\n }";
    53.    // 向当前页面注册javascript脚本代码
    54.    csm.RegisterClientScriptBlock(this.GetType(), "CallServerMethod",    
    55.     callbackScript, true);
    56. }
    57. </script>
    58. </head>
    59. <body>
    60. <form id="form1" runat="server">
    61. <table border="1" cellpadding="0" cellspacing="0" width="400px">
    62. <tr>
    63. <td width="100px">用户名</td><td><input type="text" size="10" maxlength="20" id="txtUserName" onblur="CallServerMethod(txtUserName.value,null)" /><span id="message"></span></td>
    64. </tr>
    65. <tr>
    66. <td>密码</td><td><input type="password" size="10" maxlength="20" id="txtPwd" /></td>
    67. </tr>
    68. </table>
    69. </form>
    70. </body>
    71. </html>
 
        上面的页面中我已经添加了足够详尽的注视,不过我还是要说明几点:
(1)
   1. <%@ Implements Interface="System.Web.UI.ICallbackEventHandler" %>
这句表示当前页面实现了ICallbackEventHandler接口,如果采用页面与代码分离的模式,后台cs代码则应是:
   1. public partial class Register : System.Web.UI.Page, ICallbackEventHandler
   2. {
   3. //cs代码
   4. }
(2)
   1. <input type="text" size="10" maxlength="20" id="txtUserName" onblur="CallServerMethod(txtUserName.value,null)" />
这里有一个onblur="CallServerMethod(txtUserName.value,null),表示当用户名文本框失去焦点之后激发CallServerMethod这个客户端方法,这个客户端方法是由asp.net动态生成的。
(3)
   1. csm.GetCallbackEventReference(this, "args","Success","","Error",false);
中的"Success"和"Error"分别代表客户端的javascript函数,可以在代码中见到,其中"Success"代表调用服务器端方法成功后要执行的客户端方法名,"Error"代表调用服务器端方法失败时调用的客户端方法名。
该页面在客户端生成的HTML代码如下:
     1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2. <html xmlns="http://www.w3.org/1999/xhtml">
     3. <head>
     4. <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
     5. <title>用户注册</title>
     6. <script language="javascript">
     7. //客户端执行的方法
     8. //下面的方法是接收并处理服务器方法执行的返回结果
     9. function Success(args, context)
    10. {
    11.         message.innerText    = args;
    12. }
    13. //下面的方式是当接收服务器方法处理的结果发生异常时调用的方法
    14. function Error(args, context)
    15. {
    16.         message.innerText    = '发生了异常';
    17. }
    18. </script>
    19. </head>
    20. <body>
    21. <form name="form1" method="post" action="register.aspx" id="form1">
    22. <div>
    23. <input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
    24. <input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
    25. <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMjA0MjMxNTU1OGRkIv6UMIqGy3vfPLfPRjEbuTwUrf8=" />
    26. </div>
    27. <script type="text/javascript">
    28. <!--
    29. var theForm = document.forms['form1'];
    30. if (!theForm) {
    31.         theForm = document.form1;
    32. }
    33. function __doPostBack(eventTarget, eventArgument) {
    34.         if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
    35.                 theForm.__EVENTTARGET.value = eventTarget;
    36.                 theForm.__EVENTARGUMENT.value = eventArgument;
    37.                 theForm.submit();
    38.         }
    39. }
    40. // -->
    41. </script>
    42. <script src="/WebResource.axd?d=CcZ-_AaHZnD65xnNHEUijg2&t=633578466781093750" type="text/javascript"></script>
    43. <script type="text/javascript">
    44. <!--
    45. function CallServerMethod(args, context) {
    46. WebForm_DoCallback('__Page',args,Success,"",Error,false);
    47.    }// -->
    48. </script>
    49. <table border="1" cellpadding="0" cellspacing="0" width="400px">
    50. <tr>
    51. <td width="100px">用户名</td><td><input type="text" size="10" maxlength="20" id="txtUserName" onblur="CallServerMethod(txtUserName.value,null)" /><span id="message"></span></td>
    52. </tr>
    53. <tr>
    54. <td>密码</td><td><input type="password" size="10" maxlength="20" id="txtPwd" /></td>
    55. </tr>
    56. </table>
    57. <script type="text/javascript">
    58. <!--
    59. WebForm_InitCallback();// -->
    60. </script>
    61. </form>
    62. </body>
    63. </html>

 
在生成的HTML代码中多了几段javascipt教本块,下面分别说明:
(1)第一部分
     1. <script type="text/javascript">
     2. <!--
     3. var theForm = document.forms['form1'];
     4. if (!theForm) {
     5.         theForm = document.form1;
     6. }
     7. function __doPostBack(eventTarget, eventArgument) {
     8.         if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
     9.                 theForm.__EVENTTARGET.value = eventTarget;
    10.                 theForm.__EVENTARGUMENT.value = eventArgument;
    11.                 theForm.submit();
    12.         }
    13. }
    14. // -->
    15. </script>
这部分代码是每个asp.net页面发送到客户端都会生成的,用于提交当前表单,其中eventTarget参数表示激发提交事件的控件,eventArgument参数表示发生该事件时的参数信息。
(2)第二部分
   1. <script src="/WebResource.axd?d=CcZ-_AaHZnD65xnNHEUijg2&t=633578466781093750" type="text/javascript"></script>
这部分代码是用来生成一些用于Ajax调用的js脚本。说穿了,asp.net之所以开发起来方便,是因为微软在幕后默默地为我们做了很多工作,回调的本质其实就是Ajax调用。
我们可以将“/WebResource.axd?d=CcZ-_AaHZnD65xnNHEUijg2&amp;t=633578466781093750”这部分拷贝到浏览器地址栏中,如下图:
 
回车之后会弹出一个下载文件对话框,如下图:
 
将这个页面保存到本地,虽然默认的保存文件的后缀为“.axd”,但它其实是一个文本文件,里面是一些javascript代码,我们可以用记事本打开,在里面我们可以看到“WebForm_DoCallback”这个方法,如下:
 
在这个axd文件里做了很多幕后工作,所以我们的回调才相对比较简单。
(3)第三部分
     1. <script type="text/javascript">
     2. <!--
     3. function CallServerMethod(args, context) {
     4. WebForm_DoCallback('__Page',args,Success,"",Error,false);
     5.    }// -->
     6. </script>
这部分代码是后台生成的,通过获取Page类的ClientScript属性,也就是ClientScriptManager的实例注册到页面的,里面定义了两个javascript函数:CallServerMethod函数和WebForm_DoCallback函数,并且是在 CallServerMethod函数中调用WebForm_DoCallback函数。
(4)第四部分
     1. <script type="text/javascript">
     2. <!--
     3. WebForm_InitCallback();// -->
     4. </script>
        这部分代码也是幕后生成的,这个javascript函数也可以在那个axd文件中找到。如下图:
 
        当我们在以除“admin”之外的字符串作为用户名并移开焦点之后,会得到可以注册的提示,如下图:
 
        当我们输入“admin”作为用户名时的结果:
 
        另外,我们将服务器端执行的方法做如下处理,也就是RaiseCallbackEvent(String eventArgument)这个方法,我们在这里抛出一个异常,代码如下:
     1. // 定义在服务器端运行的回调方法.
void RaiseCallbackEvent() void RaiseCallbackEvent(String eventArgument)
     3. {
     4.    /*
     5.    if(eventArgument.ToLower().IndexOf("admin")!=-1)
     6.    {
     7.     result=eventArgument+"不能作为用户名注册。";
     8.    }
     9.    else
    10.    {
    11.     result=eventArgument+"可以注册。";
    12.    }
    13.    */
    14.    throw new Exception();
    15. }
        再次运行,无论我们以什么作为用户名,都会得到如下结果:
 
        之所以会出现“发生了异常”这个字符串,是因为我们定义了function Error(args, context)这个javascript函数,并且把它作为调用服务器端方法发生异常时的客户端处理函数,它的处理方式就是显示“发生了异常”这个字符串。
        后记:看到很多朋友在http://zhoufoxcn.blog.51cto.com/上留言,急切想看到后续文章,所以不顾白天工作繁忙继续写了这一篇文章,在这篇文章里花了较大篇幅介绍回调技术,怕初学者理解起来有困难,所以我在代码中用了大量注释,并额外解释了一些关键代码,希望大家能够理解。下一篇将介绍asp.net基本服务器控件。
        写完这篇文章已经是2008-9-26 日凌晨了,我发现这个系列的文章该叫《asp.net晨话》而不是叫《asp.net夜话》了,因为每次我写完的时候都已经是凌晨了,呵呵。不过有这么多朋友热情的留言鼓励,我的动力还是很大的。下一篇我已经基本完成了,不过需要我抽空再做一下文字校正工作。工作比较忙,时间特别不充足,希望大家多多谅解:)
        另外,如果大家有什么好的建议请到我的博客http://zhoufoxcn.blog.51cto.com/留言,我会经常留意大家的留言的。
                                                                                        周金桥 2008-9-28 0点50分