最近一段时间在博客园逛的时候,关于Ajax和Atlas的讨论很多很热,看了一些例子和代码,觉得和原来1.1的Postback方式有比较大的区别,而我目前所做的系统的架构和设计都是基于1.1的,如何转移到将来的ASP.NET 2.0,使用无刷新机制来提升客户体验,同时又保留原来的架构和设计,是我最近经常想到的问题。
不久之后,我看到了几位大侠对于ASP.NET 2.0 Callback的描述,称它能够实现轻量级的Ajax,也可以保留Postback方式,这让我豁然开朗,这不就是我想要的吗?如果能够把这种方式移植到1.1中,我们的系统就不用做太大的修改就可以在需要与客户频繁交互的地方采取Callback方式来实现Ajax效果,习惯1.1的程序员也容易了解和转换到Callback方式,并作为将来学习Ajax和Atlas的
铺垫和过渡。
以下是大侠的文章
Teddy:深度解析Asp.Net2.0中的Callback机制
出走的影子:2.0正式版中callback的一些变化+使用示例(ASP.NET 2.0)
How to: Implement Callbacks in ASP.NET Web Pages
说干就干,下载.NET 2.0 Framework,随便运行一个页面,将WebResource.axd保存下来,改名为Asp2_Callback.js,客户端Callback所需要的JavaScript脚本就有了。接下来就该Reflector上场了,(真的感谢这个工具让我们能够透过表象看到ASP.NET的内在)。观察一下2.0中的Page对象,果然针对Callback添加了一些方法和属性,找出来加以改写,就成了一个基于1.1的支持Callback的AjaxPage对象了,下面是改写后的代码,帮助您揭开Callback实现之迷。
1
using
System;
2
using
System.IO;
3
using
System.Web;
4
using
System.Web.UI;
5
using
System.Text;
6
using
System.Collections;
7
using
System.Collections.Specialized;
8

9
namespace
CallbackDemo
10

{
11
/**//// <summary>
12
/// 从System.Web.UI.Page继承,扩展了对Callback的支持
13
/// </summary>
14
public class AjaxPage:System.Web.UI.Page
15
{
16
private bool m_iscallback;
17
private string m_callbackid;
18
private NameValueCollection m_requestValueCollection;
19
private ICallbackEventHandler m_callbackControl;
20
private string m_formid;
21
22
/**//// <summary>
23
/// 判断本次提交是Callback还是Postback
24
/// </summary>
25
public bool IsCallback
26
{
27
get
{return m_iscallback;}
28
}
29
30
/**//// <summary>
31
/// 回传的控件ID,此控件的RaiseCallbackEvent和GetCallbackResult方法将被触发
32
/// </summary>
33
public string CallbackID
34
{
35
get
{return m_callbackid;}
36
}
37
38
public string FormID
39
{
40
get
41
{
42
if (m_formid==null)
43
m_formid = GetFormID();
44
return m_formid;
45
}
46
set
{m_formid = value;}
47
}
48
49
public AjaxPage()
50
{
51
//如果将页面作为CallbackControl.则需要设置一个默认ID
52
this.ID = "AjaxPage";
53
m_iscallback = false;
54
m_callbackid = string.Empty;
55
m_requestValueCollection = null;
56
m_formid = null;
57
}
58
59
protected override void OnInit(EventArgs e)
60
{
61
//注册必要的脚本和方法
62
this.RegisterStartupScript("callbackscript","<SCRIPT LANGUAGE=/"javascript/" SRC=/""+this.Request.ApplicationPath+"/asp2_Callback.js/"></SCRIPT>");
63
this.RegisterStartupScript("mycallback",BuildCallbackCommand());
64
//根据Post的方式返回Request.QueryString或者Request.Form集合
65
m_requestValueCollection = this.DeterminePostBackMode();
66
if (this.m_requestValueCollection != null)
67
{
68
//检查回传值确定是否是Callback方式回传
69
m_callbackid = this.m_requestValueCollection["__CALLBACKID"];
70
if (m_callbackid != null)
71
{
72
m_iscallback = true;
73
}
74
}
75
base.OnInit(e);
76
}
77
78
protected override void OnLoad(EventArgs e)
79
{
80
base.OnLoad(e);
81
if (IsCallback)
82
PrepareCallback(CallbackID);
83
}
84
85
protected override void Render(HtmlTextWriter writer)
86
{
87
if (this.IsCallback)
88
{
89
this.RenderCallback();
90
}
91
else
92
base.Render(writer);
93
}
94
95
/**//// <summary>
96
/// 根据callbackControlID查找控件,如果该控件实现了ICallbackEventHandler接口就触发RaiseCallbackEvent方法
97
/// </summary>
98
/// <param name="callbackControlID">Callback控件ID</param>
99
private void PrepareCallback(string callbackControlID)
100
{
101
this.Response.Cache.SetNoStore();
102
try
103
{
104
string callbackparam = this.m_requestValueCollection["__CALLBACKPARAM"];
105
if (this.ID == callbackControlID)
106
this.m_callbackControl = (ICallbackEventHandler)this;
107
else
108
this.m_callbackControl = this.FindControl(callbackControlID) as ICallbackEventHandler;
109
if (this.m_callbackControl == null)
110
{
111
throw new InvalidOperationException(string.Format("Page_CallBackTargetInvalid, Callback Control ID:{0}", new object[]
{ callbackControlID }));
112
}
113
this.m_callbackControl.RaiseCallbackEvent(callbackparam);
114
}
115
catch (Exception exception)
116
{
117
this.Response.Clear();
118
this.Response.Write('e');
119
if (this.Context.IsCustomErrorEnabled)
120
{
121
this.Response.Write(string.Format("Page_CallBackError"));
122
return;
123
}
124
this.Response.Write(HttpUtility.HtmlEncode(exception.Message));
125
return;
126
}
127
}
128
129
/**//// <summary>
130
/// 如果是Callback方式则在Render中执行该方法,将m_callbackControl.GetCallbackResult()返回到客户端
131
/// </summary>
132
private void RenderCallback()
133
{
134
string cbsc = this.m_requestValueCollection["__CALLBACKLOADSCRIPT"];
135
bool flag = !(cbsc==null || cbsc==string.Empty);
136
try
137
{
138
string callbackidx = null;
139
if (flag)
140
{
141
callbackidx = this.m_requestValueCollection["__CALLBACKINDEX"];
142
if ((callbackidx==null) || (callbackidx==string.Empty))
143
{
144
throw new HttpException(string.Format("Page_CallBackInvalid"));
145
}
146
for (int idx = 0; idx < callbackidx.Length; idx++)
147
{
148
if (!char.IsDigit(callbackidx, idx))
149
{
150
throw new HttpException(string.Format("Page_CallBackInvalid"));
151
}
152
}
153
this.Response.Write("<script>parent.__pendingCallbacks[");
154
this.Response.Write(callbackidx);
155
this.Response.Write("].xmlRequest.responseText=/"");
156
}
157
if (this.m_callbackControl != null)
158
{
159
string callbackrlt = this.m_callbackControl.GetCallbackResult();
160
this.Response.Write('s');
161
this.Response.Write(callbackrlt);
162
}
163
if (flag)
164
{
165
this.Response.Write("/";parent.__pendingCallbacks[");
166
this.Response.Write(callbackidx);
167
this.Response.Write("].xmlRequest.readyState=4;parent.WebForm_CallbackComplete();</script>");
168
}
169
}
170
catch (Exception exception1)
171
{
172
this.Response.Clear();
173
this.Response.Write('e');
174
if (this.Context.IsCustomErrorEnabled)
175
{
176
this.Response.Write(string.Format("Page_CallBackError"));
177
return;
178
}
179
this.Response.Write(HttpUtility.HtmlEncode(exception1.Message));
180
}
181
}
182
183
生成Callback方法#region 生成Callback方法
184
public string GetCallbackEventReference(Control control, string argument, string clientCallback, string context)
185
{
186
return GetCallbackEventReference(control,argument,clientCallback,context,null,false);
187
}
188
189
public string GetCallbackEventReference(Control control, string argument, string clientCallback, string context, string clientErrorCallback)
190
{
191
return GetCallbackEventReference(control,argument,clientCallback,context,clientErrorCallback,false);
192
}
193
194
public string GetCallbackEventReference(Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
195
{
196
if (control == null)
197
{
198
throw new ArgumentNullException("control");
199
}
200
if (!(control is ICallbackEventHandler))
201
{
202
throw new InvalidOperationException(string.Format("Page_CallBackTargetInvalid, ID:{0}", new object[]
{ control.UniqueID }));
203
}
204
return GetCallbackEventReference("'" + control.UniqueID + "'",argument,clientCallback,context,clientErrorCallback,false);
205
}
206
207
public string GetCallbackEventReference(string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
208
{
209
if (argument == null)
210
{
211
argument = "null";
212
}
213
else if (argument.Length == 0)
214
{
215
argument = "/"/"";
216
}
217
if (context == null)
218
{
219
context = "null";
220
}
221
else if (context.Length == 0)
222
{
223
context = "/"/"";
224
}
225
return ("WebForm_MyCallback(" + target + "," + argument + "," + clientCallback + "," + context + "," + ((clientErrorCallback == null) ? "null" : clientErrorCallback) + "," + (useAsync ? "true" : "false") + ")");
226
}
227
#endregion
228
229
230
/**//// <summary>
231
/// WebForm_MyCallback是对系统提供的WebForm_DoCallback方法进行封装,以加入theForm参数
232
/// </summary>
233
/// <returns></returns>
234
private string BuildCallbackCommand()
235
{
236
StringBuilder sb = new StringBuilder();
237
sb.Append("<script type=/"text/javascript/" language=/"javascript/">/r/n");
238
sb.Append("var theForm = document.forms['"+FormID+"'];/r/n");
239
sb.Append("if (!theForm) {theForm = document."+FormID+";}/r/n");
240
sb.Append("function WebForm_MyCallback(eventTarget, eventArgument, eventCallback, context, errorCallback, useAsync)/r/n");
241
sb.Append("{/r/n");
242
sb.Append(" context.innerHTML = /"Processing
/";/r/n");
243
sb.Append(" __theFormPostData = /"/";/r/n");
244
sb.Append(" WebForm_InitCallback();/r/n");
245
sb.Append(" WebForm_DoCallback(eventTarget, eventArgument, eventCallback, context, errorCallback, useAsync)/r/n");
246
sb.Append("}/r/n");
247
sb.Append("</Script>/r/n");
248
sb.Append("/r/n");
249
return sb.ToString();
250
}
251
252
/**//// <summary>
253
/// 重写VerifyRenderingInServerForm方法,当Callback方式回传时就不再进行Verify
254
/// </summary>
255
/// <param name="control"></param>
256
public override void VerifyRenderingInServerForm(Control control)
257
{
258
if (!this.IsCallback)
259
base.VerifyRenderingInServerForm(control);
260
}
261
262
/**//// <summary>
263
/// 因为Form无法直接读取,只能采用遍历控件查找
264
/// </summary>
265
/// <returns></returns>
266
public virtual string GetFormID()
267
{
268
for (int i=0;i<this.Controls.Count;i++)
269
{
270
if (this.Controls[i] is System.Web.UI.HtmlControls.HtmlForm)
271
return (this.Controls[i] as System.Web.UI.HtmlControls.HtmlForm).ID;
272
}
273
return "Form1";
274
}
275
276
}
277
}
278
ICallbackEventHandler接口
/**/
/// <summary>
/// 希望具有Callback功能的控件必须实现此接口
/// </summary>
public
interface
ICallbackEventHandler

{
string GetCallbackResult();
void RaiseCallbackEvent(string eventArgument);
}
关键的方法
PrepareCallback(CallbackID)
系统以Callback方式回传时执行此方法,根据返回的CallbackID查找相应的CallbackCtrl,传入callbackparam并执行其RaiseCallbackEvent方法。
RenderCallback()
系统以Callback方式回传时将不会执行原来的Render方法而改而执行Rendercallback,它的作用就是将callbackCtrl的GetCallbackResult()方法的结果返回到客户端交由脚本继续执行。
GetCallbackEventReference()
生成调用Callback方式需要的脚本,封装了2.0提供的WebForm_DoCallback方法,并可以加入自己的脚本语句。
要注意的地方
FormID
Form对象无法访问,只能通过外部指定或者通过遍历Controls查找,期待更好的方法
theForm
相对2.0,1.1在客户端脚本自动生成的是theform,所以在BuildCallbackCommand()方法中生成theForm对象,否则Callback脚本无法执行.
VerifyRenderingInServerForm()
重写该方法使Callback下不进行此验证以保证当我们使用RendControl()生成Html代码返回时,某些服务端控件不会强制验证而抛出异常
WebForm_InitCallback()
此方法执行后将收集页面的回传信息,使得Callback方式回传后可以使用Request.Form[key]方式直接访问.
完成了AjaxPage的封装后,只要继承于它就可以在项目中象2.0一样轻松使用Callback功能了,如何使用在大侠们的文章里已经有详细的说明和示例,这里就不多说了,直接奉上一个例子,演示同时执行Postback和Callback,可以看出后台的结构和代码并不需要很大的修改就可以作出无刷新页面的效果。本例需要连接MSSQLServer的NorthWind数据库,本地编译前请先修改数据库连接字符串。
源代码和例子下载
http://www.cnblogs.com/Files/abei108/CallbackDemo.rar
示例缩略图

写在最后
这是我在网上认真写的第一篇博客,文笔有限加上仓促写成,如有错误请大家包涵,转载请注明出处。谢谢
http://abei108.cnblogs.com/archive/2006/06/10/422125.html