n
ASP.NET1.1中的Postback机制
n ASP.NET2.0中的新功能Script Callback介绍
n 如何使用Script Callback IN ASP.NET 2.0
n 如何使用Script Callback IN ASP.NET 1.1
n 思考与问题
n 作者信息
ASP.NET1.1中的Postback机制
在asp.net1.1中,我们知道每个服务器控件都有一个AutoPostback属性。它的作用是当用户(客户端)修改该控件的值,即也可以说是当该控件的事件触发的时候,客户端就通过JavaScript:__doPostback(object,EventArgument)来和服务端实现通信。它使得程序员在实现动态的获取服务端数据变得非常方便。例如:有一个查询成绩的页面,由一个DrowDownlist和一个DataGrid来实现。其中DropDownlist从服务绑定所要查看的学期(2003-2004上半学期,2003-2004下半学期等),DataGrid而是用来显示他在改学期所有科目的成绩,和该学科的学分。在ASP.NET1.1中我们最常使用的方法也就是将DrowDownlist的AutoPostback属性设置为True,并把该事件触发服务端后台的某个方法,该方法获取所选择的学期的id号,然后去数据库获得DataTable重新绑定与DataGrid并将它显示出来。在这整个过程中,作为用户肯定是要经历一个刷新页面的过程。如果仅像刚才那个例子所说这个刷新不会很大的影响用户的Experience,不过如果你有一个复杂的填写表单的页面,该页面有大量的Control是相关的,需要更据其它Control的选择情况去动态的绑定数据,那当用户填完这个表单的时候也许眼睛也花了,他也许再也不想经历如此痛苦的折磨了,从Experience方面来讲将是非常差的。在不是从服务端获取大量数据的情况下,我们要如何才能避免这种在用户看似多余的Postback呢?
ASP.NET2.0中新功能Script Callback介绍
在ASP.NET2.0中,客户端的脚本功能已经被扩展了。并且增加了Script Callbacks(通过脚本建立于后台的链接,后文将其翻译为客户端呼叫)。你可以用程序去控制<head>标签,通过程序控制input焦点,读取或者设置页面的标题,并且可以控制button或其他的控件提交到其他任何页面(in the application)。具体您可以查看Beta 1 说明,里面有例子和参考。
为了使用ASP.NET2.0中的客户端呼叫技术,你需要在页面中定义一个触发元件(不是提交按钮Submit button)并且把它绑定上JavaScript代码。这段代码会重新获得当前页面的input数据并且准备去呼叫系统提供的一个称为WebForm_DoCallback的Script函数(Beta1)。这个函数会建立一个和一个指定的远程ASP.NET页面建立HTTP连接。后台侦听到这个来自客户端的呼叫后呢,对此触发一个方法。服务端通过先前的客户端的函数返回一个值。在客户端,通过一个用户自定义的脚本函数来获得服务端的值并且用DHTML将其呈现在页面上。重要的是,这样做客户端和服务端的通信仍旧在进行,但是页面并没有重新刷新。更重要的是,当客户端在获取数据的时候用户仍旧可以在它原来的页面上操作。图片1,显示了客户端呼叫的流程。
![Figer1](https://i-blog.csdnimg.cn/blog_migrate/08ddfbe160d0525dcd27f73fd4839030.jpeg)
图片1
如何使用Script Callback IN ASP.NET 2.0
在ASP.NET2.0中使用Script Callback 主要有两个步骤。首先,需要写服务端的代码,这段代码将会被客户端所调用。当然,必须前定义数据你将要返回的数据类型。这就要更具你需要声么数据而定了。实际上他们通信的都是用String的,但是这个字符串的内容就可以是多种多样的——JavaScript,XML,用’,’分隔的string值,等等。
然后,你需要写客户端的代码去引起一个无刷新的呼叫。远程的响应是建立在一个叫WebForm_DoCallback的JavaScript函数。你其实没有必要去知道这个函数的名字,你可以从Page类的新成员——GetCallbackEventReference()中获得客户端呼叫。
![Finger2](https://i-blog.csdnimg.cn/blog_migrate/945c8fb7c1a30905440a5a5570ff8fb4.jpeg)
string callbackRef = Page.ClientScript.GetCallbackEventReference(this, "document.all['DropDownList1'].value", "UpdateInfo", "null");
注意:在我当前的版本中,也是最新的版本中GetCallbackEventReference不是Page的一个属性而是Page.ClientScript属性的一个方法,该方法有三个重载详见msdn。
而呼叫这个javaScript函数的是绑定在一个可以点击的元素上,例如<button>标记。所以这个可以点击的元素,不能是<asp:button>控件,因为这样的服务端控件客户端呈现为一个提交按钮:
<input type=”submit” id=”button” …>
置于ASP.NET2.0中是如何实现Callback全过程在此省略,详情请看作者原文。
需要说明的是:为了能在服务端检测到页面元素使用了Script Callback 必须在页面中引用以下接口,在ASP.NET1.1中也如此。
<%@ Impements Interface=”System.Web.UI.ICallbackEventHandler” %>
如果控件实现了这个接口,ASP.NET后台就调用RaiseCallbackEvent 方法,并且针对呼叫返回响应。
如下是ICallbackEventHandler 接口中的一个方法:
string RaiseCallbackEvent(string eventArgumen)
当然这个string 是可以表示为任何你所需要的值,如前面所说的XML,JAVASCRIPT,数值,等等。
以下是我仿照作者的Demo更具最新的Freamwork的一个例子:
scriptCallback.aspx:
后台代码如下:scriptCallback.aspx.cs
显示时效如下图:
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class ScriptCallback_aspx : System.Web.UI.Page
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0be121fa5b8988fbabbbc526af3b0fc0.gif)
{
protected void Page_Load(object sender, EventArgs e)
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
string callbackRef = Page.ClientScript.GetCallbackEventReference(this, "document.all['DropDownList1'].value", "UpdateInfo", "null");
//给buttom按钮添加事件,该事件由GetCallbackEventReference产生。
btn.Attributes["onclick"] = String.Format("javascript:{0}",callbackRef);
}
public virtual string RaiseCallbackEvent(string eventArgument)
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
//int userID = Convert.ToInt16(eventArgument);
//此处本该是更具eventArgument去数据库获取数据的,作为演示就省去了数据连接,直接//更具不同的eventArgument显示既定的数据即可。
string[] buf = new string[6];
buf[0] = eventArgument + "'s ID";
buf[1] = eventArgument;
buf[2] = eventArgument + "'s Password";
buf[3] = eventArgument + "'s Authority";
buf[4] = eventArgument + "'s BlogName";
buf[5] = eventArgument + "'s Email";
return String.Join(",", buf);
}
}
using
System;
using
System.Data;
using
System.Configuration;
using
System.Collections;
using
System.Web;
using
System.Web.Security;
using
System.Web.UI;
using
System.Web.UI.WebControls;
using
System.Web.UI.WebControls.WebParts;
using
System.Web.UI.HtmlControls;
public
partial
class
ScriptCallback_aspx : System.Web.UI.Page
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
protected void Page_Load(object sender, EventArgs e)
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
string callbackRef = Page.ClientScript.GetCallbackEventReference(this, "document.all['DropDownList1'].value", "UpdateInfo", "null");
//给buttom按钮添加事件,该事件由GetCallbackEventReference产生。
btn.Attributes["onclick"] = String.Format("javascript:{0}",callbackRef);
}
public virtual string RaiseCallbackEvent(string eventArgument)
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
//int userID = Convert.ToInt16(eventArgument);
//此处本该是更具eventArgument去数据库获取数据的,作为演示就省去了数据连接,直接//更具不同的eventArgument显示既定的数据即可。
string[] buf = new string[6];
buf[0] = eventArgument + "'s ID";
buf[1] = eventArgument;
buf[2] = eventArgument + "'s Password";
buf[3] = eventArgument + "'s Authority";
buf[4] = eventArgument + "'s BlogName";
buf[5] = eventArgument + "'s Email";
return String.Join(",", buf);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/fae3286fa01208dbddf5e3bd1648ce0d.jpeg)
为了将此新特新运用在ASP.NET1.1中,我们来挖掘一下,在.NET2.0中到底是如何实现的。
以下是跟踪过程:用birdsome介绍的方法点击more info前打开在下一步按Alt->v->u->b然后点击more info:
<button id="btn" onclick="javascript:WebForm_DoCallback('__Page',document.all['DropDownList1'].value,UpdateInfo,null,null,false)">More Info</button>
从此处可以知道button已经绑定上了由GetCallbackEventReference产生的脚本。在这个版本中并没有这个脚本的名字还是叫WebForm_DoCallback,Never Mind!继续,F11……
<script src="/ExperienceAspx2.0/WebResource.axd?a=s&r=WebForms.js&t=632430413780000000" type="text/javascript"></script>
我们还发现在页面中系统加上了上面这段脚本。WebResource.axd是一个在ASP.NET中新的内置于HTTP的处理中。用它把脚本程序包含到页面中。这个handler确保把所有的控件或者页面的Callback引用脚本包含进来。并且,webResource.axd确保所有的WebForm_DoCallback和WebForm_InitCallback成功的被执行。
下面我们就揭开它的真面目:
这个版本的WebResource.axd中包含的函数有:
function WebForm_PostBackOptions(eventTarget, eventArgument, validation, validationGroup, actionUrl, trackFocus, clientSubmit)
function WebForm_DoPostBackWithOptions(options)
function WebForm_DoCallback(eventTarget, eventArgument, eventCallback, context, errorCallback, useAsync)
function WebForm_CallbackComplete()
function WebForm_InitCallback()
function WebForm_ReEnableControls()
function WebForm_ReDisableControls()
function WebForm_FireDefaultButton(event, target)
function WebForm_GetScrollX()
function WebForm_GetScrollY()
function WebForm_SaveScrollPositionSubmit()
function WebForm_SaveScrollPositionOnSubmit()
function WebForm_RestoreScrollPosition()
function WebForm_TextBoxKeyHandler()
我们只研究其中的WebForm_DoCallback,和WebForm_CallbackComplete,其他完整的代码我将在提供打包下载。
var __callbackObject = new Object();
function WebForm_DoCallback(eventTarget, eventArgument, eventCallback, context, errorCallback, useAsync) {
re = new RegExp("//x2B", "g");
var postData = __theFormPostData +
"__CALLBACKID=" + eventTarget +
"&__CALLBACKPARAM=" + escape(eventArgument).replace(re, "%2B");
if (__nonMSDOMBrowser) {
var xmlRequest = new XMLHttpRequest();
if (pageUrl.indexOf("?") != -1) {
xmlRequest.open("GET", pageUrl + "&" + postData, false);
}
else {
xmlRequest.open("GET", pageUrl + "?" + postData, false);
}
xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlRequest.send(null);
response = xmlRequest.responseText;
if (response.charAt(0) == "s") {
if (eventCallback != null) {
eventCallback(response.substring(1), context);
}
}
else {
if (errorCallback != null) {
errorCallback(response.substring(1), context);
}
}
}
else {
var xmlRequest = new ActiveXObject("Microsoft.XMLHTTP");
xmlRequest.onreadystatechange = WebForm_CallbackComplete;
__callbackObject.xmlRequest = xmlRequest;
__callbackObject.eventCallback = eventCallback;
__callbackObject.context = context;
__callbackObject.errorCallback = errorCallback;
var usePost = false;
if (pageUrl.length + postData.length + 1 > 2067) {
usePost = true;
}
if (usePost) {
xmlRequest.open("POST", pageUrl, useAsync);
xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlRequest.send(postData);
}
else {
if (pageUrl.indexOf("?") != -1) {
xmlRequest.open("GET", pageUrl + "&" + postData, useAsync);
}
else {
xmlRequest.open("GET", pageUrl + "?" + postData, useAsync);
}
xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlRequest.send();
}
}
}
可见在该版本中比作者原文的WebForm_DoCallback有了很大的改进。
var
__callbackObject
=
new
Object();
function
WebForm_DoCallback(eventTarget, eventArgument, eventCallback, context, errorCallback, useAsync) {
re
=
new
RegExp(
"
//x2B
"
,
"
g
"
);
var
postData
=
__theFormPostData
+
"
__CALLBACKID=
"
+
eventTarget
+
"
&__CALLBACKPARAM=
"
+
escape(eventArgument).replace(re,
"
%2B
"
);
if
(__nonMSDOMBrowser) {
var
xmlRequest
=
new
XMLHttpRequest();
if
(pageUrl.indexOf(
"
?
"
)
!=
-
1
) {
xmlRequest.open(
"
GET
"
, pageUrl
+
"
&
"
+
postData,
false
);
}
else
{
xmlRequest.open(
"
GET
"
, pageUrl
+
"
?
"
+
postData,
false
);
}
xmlRequest.setRequestHeader(
"
Content-Type
"
,
"
application/x-www-form-urlencoded
"
);
xmlRequest.send(
null
);
response
=
xmlRequest.responseText;
if
(response.charAt(
0
)
==
"
s
"
) {
if
(eventCallback
!=
null
) {
eventCallback(response.substring(
1
), context);
}
}
else
{
if
(errorCallback
!=
null
) {
errorCallback(response.substring(
1
), context);
}
}
}
else
{
var
xmlRequest
=
new
ActiveXObject(
"
Microsoft.XMLHTTP
"
);
xmlRequest.onreadystatechange
=
WebForm_CallbackComplete;
__callbackObject.xmlRequest
=
xmlRequest;
__callbackObject.eventCallback
=
eventCallback;
__callbackObject.context
=
context;
__callbackObject.errorCallback
=
errorCallback;
var
usePost
=
false
;
if
(pageUrl.length
+
postData.length
+
1
>
2067
) {
usePost
=
true
;
}
if
(usePost) {
xmlRequest.open(
"
POST
"
, pageUrl, useAsync);
xmlRequest.setRequestHeader(
"
Content-Type
"
,
"
application/x-www-form-urlencoded
"
);
xmlRequest.send(postData);
}
else
{
if
(pageUrl.indexOf(
"
?
"
)
!=
-
1
) {
xmlRequest.open(
"
GET
"
, pageUrl
+
"
&
"
+
postData, useAsync);
}
else
{
xmlRequest.open(
"
GET
"
, pageUrl
+
"
?
"
+
postData, useAsync);
}
xmlRequest.setRequestHeader(
"
Content-Type
"
,
"
application/x-www-form-urlencoded
"
);
xmlRequest.send();
}
}
}
可见在该版本中比作者原文的WebForm_DoCallback有了很大的改进。
在原文中作者给出了两个不要直接调用WebForm_oCallback的理由,第一是可以通过Client Script Mangner来管理用其GetCallbackEventReference方法来实现客户端的脚本,第二由于,WebForm_DoCallback不是Page API的所以在以后的版本中不能保证它不改名字,并且,如果其有新的修改也不会有对外公布的必要。
从该代码中可以发现它不仅适用于ie,对其他浏览器也提供了支持。它使用了一个COM对象从特定的URL中来接受发送数据。
然后在HTTP传送数据时是选择POST,还是GET是取决于数据的大小。如果大于2K的数据则采用POST。HTTP请求包含3个基本元素:__CALLBACKID,__CALLBACKPARAM,和发送的数据包。__CALLBACKID中包含目标地址,而__CALLBACKPARAM则是携带者服务段方法需要的参数。发送的数据包氏通过WebFrom_InitCallback方法来连接并且附加HTTP命令中。以上代码详细呈现了该实现过程。
如何使用Script Callback IN ASP.NET 1.1
到目前为止,我想我们把它搬到1.1中来使用也不是非常困难的事情。下面根据我的实践结果来讨论如何在Script Callback。
首先介绍我第一次错误的使用方法。
根据2.0的思路,第一感觉就是我把它的那些系统生成的脚本,我自己给他挖掘出来,添加到客户端,然后在后台添加自己的GetCallbackReference(),并实现RaiseCallback()两个方法,不就是一个1.1版本的Script Callback。
首先我根据vs2005的简单的msdn,以及跟踪其返回值,写了个假的GetCallbackReference();代码如下:
protected string GetCallbackEventReference(System.Web.UI.Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
string[] ReturnValue = new string[6];
if(control.ID != null)
ReturnValue[0] = control.ID.ToString();
else
ReturnValue[0] = "'__Page'";
ReturnValue[1] = argument;
ReturnValue[2] = clientCallback;
ReturnValue[3] = context!=null?context:"null";
ReturnValue[4] = clientErrorCallback!=null?clientErrorCallback:"null";
ReturnValue[5] = "false";
return String.Format("WebForm_DoCallback({0})",String.Join(",",ReturnValue));
}
protected
string
GetCallbackEventReference(System.Web.UI.Control control,
string
argument,
string
clientCallback,
string
context,
string
clientErrorCallback,
bool
useAsync)
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
string[] ReturnValue = new string[6];
if(control.ID != null)
ReturnValue[0] = control.ID.ToString();
else
ReturnValue[0] = "'__Page'";
ReturnValue[1] = argument;
ReturnValue[2] = clientCallback;
ReturnValue[3] = context!=null?context:"null";
ReturnValue[4] = clientErrorCallback!=null?clientErrorCallback:"null";
ReturnValue[5] = "false";
return String.Format("WebForm_DoCallback({0})",String.Join(",",ReturnValue));
}
本来想对其6个参数作详细介绍的,可是考虑到这不是正确的方法,所以在此就不在叙述,如果想知道的朋友可以查看msdn2.library.com(这里的还是旧版本),或者查看vs2005的对象浏览器。
类似2.0中在Page_Load作如下引用:
String callbackRef = this.GetCallbackEventReference(this,"document.all['DropDownList1'].value","MoreInfo",null,null,false);
btn.Attributes["onclick"] = String.Format("javascript:{0}",callbackRef);
并将我提供的的所有2.0中的系统脚本加到客户端中,可是调试的时候确发现,我手工添加的脚本和服务端添加的脚本存在的区别就在于编译的顺序不一样,当编译客户端的脚本的时候服务端的脚本还没有生成,也就是说,它无法调用theForm,postUrl等变量,于是我想到,是不是可以在后台在Page_Init的时候以编程的方式加入到客户端,必须确保它是在服务端脚本加入后而执行的,至于放在Page_Init中还是在Page_PreRender()我不是很清楚。
好错误的方法讨论到此,下面介绍作者的方法,也是正确的方法,起初我想用2.0中原有的脚本也许更强大,更完善。作者的方法很简单。简单的实现了xmlHTTP的客户端与服务端的通信,但是不支持其它版本的浏览器。
后台ScriptCallback.aspx.cs:
function MoreInfo1()
{
var selectedID = document.all["DropDownList1"].value;
var xml = DoCallback("ScriptCallback.aspx",selectedID);
var o = xml.responseText.split(",");
e_ID.innerHTML = o[0];
e_UserName.innerHTML = o[1];
e_Password.innerHTML = o[2];
e_Authority.innerHTML = o[3];
e_BlogName.innerHTML = o[4];
e_Email.innerHTML = o[5];
}
function DoCallback(url,params)
{
var pageUrl = url + "?callback=true¶m=" + params;
var xmlRequest = new ActiveXObject("Microsoft.XMLHTTP");
xmlRequest.open("POST",pageUrl,false);
xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlRequest.send(null);
return xmlRequest;
}
其中e_ID,e_UserName等类似分别是我呈现表格的第二列的cell的ID。
function
MoreInfo1()
{
var
selectedID
=
document.all[
"
DropDownList1
"
].value;
var
xml
=
DoCallback(
"
ScriptCallback.aspx
"
,selectedID);
var
o
=
xml.responseText.split(
"
,
"
);
e_ID.innerHTML
=
o[
0
];
e_UserName.innerHTML
=
o[
1
];
e_Password.innerHTML
=
o[
2
];
e_Authority.innerHTML
=
o[
3
];
e_BlogName.innerHTML
=
o[
4
];
e_Email.innerHTML
=
o[
5
];
}
function
DoCallback(url,params)
{
var
pageUrl
=
url
+
"
?callback=true¶m=
"
+
params;
var
xmlRequest
=
new
ActiveXObject(
"
Microsoft.XMLHTTP
"
);
xmlRequest.open(
"
POST
"
,pageUrl,
false
);
xmlRequest.setRequestHeader(
"
Content-Type
"
,
"
application/x-www-form-urlencoded
"
);
xmlRequest.send(
null
);
return
xmlRequest;
}
其中e_ID,e_UserName等类似分别是我呈现表格的第二列的cell的ID。
在1.1中的效果如下:
![Figer4 Figer4.JPG](https://i-blog.csdnimg.cn/blog_migrate/c5633f7b166337dd2f0d129477fbc189.jpeg)
至此我们实现了在ASP.NET1.1中的无postback的和服务端的提交获取数据。原理就是利用了xmlHTTP。不过我们还是更期待在2.0中来使用该功能。
思考与问题
整个研究学习过程中,我逃避了两点。第一,对2.0中的脚本程序没有做细致的分析,它到底是如何运行的,第二,如果我一定要用服务端的脚本,我如何通过后台把脚本以程序方式加入到客户端,并且在呈现客户端页面后再调用。如果你有兴趣的话,可以和我一起研究哦。
个人觉得,在该文章上发布的时候已近有人提出来这样做的安全性,我觉得很有道理,如此所有的通信就相当于明码在这样传来传去,对于传一些比较重要的数据的时候这样做是很危险的,我也不是很清楚2.0中有没有对这个问题提供其它可靠的有安全保障的方法。但如果把这个方法用在简单的更具客户的不同要求而时时地对页面中某些元素进行验证,少量的数据绑定,这可以算是一个不错的解决方法!如果有大量数据要通过此方法和服务端通信,是不是可以加一个loading bar,让更新数据部分的元素停止操作,而用户仍然可以操作其它控件这样的二次开发,我还没有尝试,我想不久就会尝试。所以做gon功能简单,要把功能做出漂亮的UI就很难,如果要在漂亮的UI上加上user experience就是难上加难啊!
作者信息
在此注册blog一直没有发表任何文章,我一直觉得自己水平还不够,这次下了决心,参考msdn的文章,然后加上自己总结实践,凑成了这篇文章,希望能给各位有所赏,有所析,有所用。小弟在此献丑了,我想我以后主要还是这种方式来提交和大家一起学习进步。这样我自己也能有很大收获,也能给大家带来一点内容。
关于我自己,我现在就读杭州电子科技大学的通信工程学院,2005年就是大三下了,不知道这儿大学生多么?希望能和各位交个朋友。
生活博客:http://spaces.msn.com/members/rbmnetatmsn/
常用Email:rbmxxx at hotmail.com