asp.net夜话之五:Page类和回调技术
在今天我主要要介绍的有如下知识点:
Page类介绍
Page的生命周期
IsPostBack属性
ClientScriptManager类
回调技术(CallBack)
其中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" %>
@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控件也有与asp.net类似的生命周期,如果aspx页面中包含有asp.net服务器控件,那么在调用页面的方法时也会调用控件的相关方法。
另外,Web应用程序是无状态的。每次请求一个新网页或者刷新页面服务器都会创建一个当前页的新实例,这就意味着无法获取页面的以前的信息,如果确实需要这么做,需要采用额外的机制。
我们将刚才新建的Index.aspx页面中添加代码,如下:
1. <%@ Page Language="C#" %>
2.
3. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
4.
5. <script runat="server">
6. string date;
7. protected void Page_Load(object sender, EventArgs e)
8. {
9. if (date == null)//如果date为空则设置为当前时间的字符串形式
10. {
11. date = DateTime.Now.ToString();
12. }
13. Response.Write("当前时间:"+date);
14. }
15. </script>
16.
17. <html xmlns="http://www.w3.org/1999/xhtml" >
18. <head runat="server">
19. <title>无标题页</title>
20. </head>
21. <body>
22. <form id="form1" runat="server">
23. <div>
24.
25. </div>
26. </form>
27. </body>
28. </html>
在Page类中有一个ClientScript属性,它是ClientScriptManager的实例,这个类是在asp.net2.0中新增的。ClientScriptManager有如下几个常用方法:
RegisterClientScriptBlock方法:向 Page 对象注册客户端脚本。
RegisterStartupScript方法:向 Page 对象注册启动脚本。
ClientScriptManager类通过键string和Type来唯一标识脚本。具有相同类型的键和Type的脚本识为同一脚本。
下面对Home窗体的Page_Load事件中输入如下代码:
可以看出上面的两个方法输出的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. //下面的方法是接收并处理服务器方法执行的返回结果
13. function Success(args, context)
14. {
15. message.innerText = args;
16. }
17. //下面的方式是当接收服务器方法处理的结果发生异常时调用的方法
18. function Error(args, context)
19. {
20. message.innerText = '发生了异常';
21. }
22. </script>
23.
24. <script language="c#" runat="server">
25. string result="";
26. // 定义在服务器端运行的回调方法.
27. public void RaiseCallbackEvent(String eventArgument)
28. {
29.
30. if(eventArgument.ToLower().IndexOf("admin")!=-1)
31. {
32. result=eventArgument+"不能作为用户名注册。";
33. }
34. else
35. {
36. result=eventArgument+"可以注册。";
37. }
38.
39. //throw new Exception();
40. }
41.
42. //定义返回回调方法执行结果的方法
43. public string
44. {
45. return result;
46. }
47.
48. //服务器上执行的方法
49. public void Page_Load(Object sender,EventArgs e)
50. {
51. // 获取当前页的ClientScriptManager的引用
52. ClientScriptManager csm = Page.ClientScript;
53.
54. // 获取回调引用。会在客户端生成WebForm_DoCallback方法,调用它来达到异步调用。这个方式是微软写的方法,会被发送到客户端
55. //注意这里的"Success"和"Error"两个字符串分别客户端代码中定义的两个javascript函数
56. //下面的方法最后一个参数的意义:true表示执行异步回调,false表示执行同步回调
57. String reference = csm.GetCallbackEventReference(this, "args","Success","","Error",false);
58. 获取一个对客户端函数的引用;调用该函数时,将启动一个对服务器端事件的客户端回调。
59. String callbackScript = "function CallServerMethod(args, context) {/n" +
60. reference + ";/n }";
61. // 向当前页面注册javascript脚本代码
62. csm.RegisterClientScriptBlock(this.GetType(), "CallServerMethod",
63. callbackScript, true);
64. }
65. </script>
66. </head>
67. <body>
68. <form id="form1" runat="server">
69. <table border="1" cellpadding="0" cellspacing="0" width="400px">
70. <tr>
71. <td width="100px">用户名</td><td><input type="text" size="10" maxlength="20" id="txtUserName" onblur="CallServerMethod(txtUserName.value,null)" /><span id="message"></span></td>
72. </tr>
73. <tr>
74. <td>密码</td><td><input type="password" size="10" maxlength="20" id="txtPwd" /></td>
75. </tr>
76. </table>
77. </form>
78. </body>
79. </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. //下面的方法是接收并处理服务器方法执行的返回结果
10. function Success(args, context)
11. {
12. message.innerText = args;
13. }
14. //下面的方式是当接收服务器方法处理的结果发生异常时调用的方法
15. function Error(args, context)
16. {
17. message.innerText = '发生了异常';
18. }
19. </script>
20.
21.
22. </head>
23. <body>
24. <form name="form1" method="post" action="register.aspx" id="form1">
25. <div>
26. <input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
27. <input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
28. <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMjA0MjMxNTU1OGRkIv6UMIqGy3vfPLfPRjEbuTwUrf8=" />
29. </div>
30.
31. <script type="text/javascript">
32. <!--
33. var theForm = document.forms['form1'];
34. if (!theForm) {
35. theForm = document.form1;
36. }
37. function __doPostBack(eventTarget, eventArgument) {
38. if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
39. theForm.__EVENTTARGET.value = eventTarget;
40. theForm.__EVENTARGUMENT.value = eventArgument;
41. theForm.submit();
42. }
43. }
44. // -->
45. </script>
46.
47.
48. <script src="/WebResource.axd?d=CcZ-_AaHZnD65xnNHEUijg2&t=633578466781093750" type="text/javascript"></script>
49.
50.
51. <script type="text/javascript">
52. <!--
53. function CallServerMethod(args, context) {
54. WebForm_DoCallback('__Page',args,Success,"",Error,false);
55. }// -->
56. </script>
57.
58. <table border="1" cellpadding="0" cellspacing="0" width="400px">
59. <tr>
60. <td width="100px">用户名</td><td><input type="text" size="10" maxlength="20" id="txtUserName" onblur="CallServerMethod(txtUserName.value,null)" /><span id="message"></span></td>
61. </tr>
62. <tr>
63. <td>密码</td><td><input type="password" size="10" maxlength="20" id="txtPwd" /></td>
64. </tr>
65. </table>
66.
67.
68. <script type="text/javascript">
69. <!--
70.
71. WebForm_InitCallback();// -->
72. </script>
73. </form>
74. </body>
75. </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&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.
4. WebForm_InitCallback();// -->
5. </script>
这部分代码也是幕后生成的,这个javascript函数也可以在那个axd文件中找到。如下图:
当我们在以除“admin”之外的字符串作为用户名并移开焦点之后,会得到可以注册的提示,如下图:
另外,我们将服务器端执行的方法做如下处理,也就是RaiseCallbackEvent(String eventArgument)这个方法,我们在这里抛出一个异常,代码如下:
1. // 定义在服务器端运行的回调方法.
2. public 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函数,并且把它作为调用服务器端方法发生异常时的客户端处理函数,它的处理方式就是显示“发生了异常”这个字符串