我的AjaxPage对象(Asp.NET 1.1)-借用2.0的Callback实现无刷新回调


最近一段时间在博客园逛的时候,关于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实现之迷。

  1using System;
  2using System.IO;
  3using System.Web;
  4using System.Web.UI;
  5using System.Text;
  6using System.Collections;
  7using System.Collections.Specialized;
  8
  9namespace 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方法
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数据库,本地编译前请先修改数据库连接字符串。

源代码和例子下载
https://files.cnblogs.com/abei108/CallbackDemo.rar
注意:AjaxPage的功能已经扩展,请访问我的AjaxPage对象续-一分钟让你的UserControl变成UpdatePanel

示例缩略图


写在最后
这是我在网上认真写的第一篇博客,文笔有限加上仓促写成,如有错误请大家包涵,转载请注明出处。谢谢

posted on 2006-06-10 01:21  Gao.Steven  阅读(2302)  评论(9编辑  收藏  举报

导航