ArcGIS Server .NET ADF中的AJAX之深入浅出/CallbackResult详解

来源:互联网 发布:华为云计算怎么样 编辑:程序博客网 时间:2024/04/30 04:50
论坛中已有不少关于.NET ADF中AJAX的精彩讲解,但切入点有些抽象。本文试图从一个初学者的角度着眼,从AJAX讲起,由浅入深,去掉不必要的技术细节,使大家对.NET ADF中AJAX有清晰的理解。再辅以前人的教程,由此能更充分地掌握.NET ADF中AJAX的工作原理。
      经常在论坛上看到很多朋友问:为什么我已经创建了一个callbackresult,但是浏览器中却没有任何反应?或者我已经把ADF控件放到了 UpdatePanel里,但为什么还是达不到我想要的效果?9.3版本的.NET Web ADF发布近2年的时间里,ESRI从来没有说过:“很抱歉,我们ADF中的AJAX工作流程有时候可能会出现一点问题”,这证明.NET Web ADF中的AJAX还是足够可靠的,事实是问题只可能出现在我们的代码里。甚至在你根本还没有弄清如何来正确使用ADF中的AJAX时,就模仿别人的代码,寄希望于你也不知道为什么要创建的一个callbackresult就能够好好的为你工作,这不应该是经常和只知道循规蹈矩的机器打交道的人所做的事情。
      既然你已经选择了.NET ADF,并想利用好其中的AJAX能力,那么弄清楚它的原理应该是必要的,可以从本文第二部分开始阅读;但也许你是一个ArcGIS Server的新手,甚至是ASP.NET的新手,不得不在短时间内完成你手上的GIS任务,并不关心它的原理,那你可以阅读本文的前两部分或者只是第二部分。(如果你是一个网页开发的新手,那么请自行学习HTML+CSS+Javascript)
第一部分:1分钟AJAX时间
      典型的,ASP.NET 2.0之前的网页,当客户端发送请求的时候,比如点击“开始处理”按钮,会将整个页面信息提交到服务器上这个页面对应的类进行处理(发送的请求叫做 postback),这个页面类处理完成后将结果(包括页面原有的信息)输出成浏览器能够识别的HTML+CSS+Javascript返回客户端,你才会看到结果页面。提交请求后到结果页面呈现前,你的鼠标会呈沙漏状,无法操作页面;结果页面呈现的一瞬间,你会看到整个页面完全刷新一次。AJAX的出现就是为了解决这个糟糕的过程,有了AJAX本领的页面,当你点击“开始处理”按钮后,你依然能够对页面其他部分进行操作,结果呈现的时候也不会刷新全部页面,而只是悄悄修改需要改变的部分。目前互联网上大多数页面都有AJAX能力,但不要迷恋AJAX,它只是个效果的名称而已,你需要感谢的是 ASP.NET中实现AJAX的两个功臣:ASP.NET 2.0时候出现的Client Callback和ASP.NET 2.0的一个扩展ASP.NET AJAX。但也不要迷恋ASP.NET,因为在最下面真正干苦力活的是Javascript里的XMLHttpRequest对象,前两者是对其不同程度的封装。
      最后重复一下,Client Callback和ASP.NET AJAX是并列关系,根据你的需要取其一作为你实现AJAX的手段即可。
第二部分:如何正确使用CallbackResult
      任何想正确使用CallbackResult的人应该都不会拒绝我先解释一下究竟什么是CallbackResult,那就先来看看它是什么。
      在只有一个Map Control和Toolbar Control页面中,无论你如何操作地图,或者使用Toolbar上的按钮和工具与地图交互,都不会刷新整个页面,这证明所有Web ADF的控件具有AJAX能力,因为其他控件也是如此。Web ADF中的Server Control是如何实现这一点呢?无疑是利用了Client Callback(9.2版本和以上可用)或者ASP.NET AJAX(9.3版本和以上可用)。究竟怎么利用?这个问题先别急,让我们深入一点点。
      针对Web ADF控件,先来看一个普通的操作:鼠标拉框放大地图。它的整个完成过程是这样的:用户在客户端的地图上(其实是ESRI自己封装的名为Map的Javascript对象,属于Web ADF Javascript中的内容)用鼠标拉一个框,松开鼠标的时候地图对象会将一些信息,比如这个框框的范围等,发送到服务器端,服务器端对应的Map控件会根据服务器端当前Map的范围和框框的范围做计算,保留新的范围在服务器的Map控件上。服务器端的过程容易完成,但这些服务器端控件还需要负责任地将这些结果同步到客户端去,否则客户端看不到任何变化。在这个例子中服务器端将结果同步到客户端的过程是,由地图服务输出新的地图图像(如果是缓存地图服务,则给出新范围的切片)传送给客户端。如下图所示:


ADFAJAX1.jpg
      如果不细心可以直接看下一段。如果细心,你可能会问为什么一个放大操作,上图会出现那么多回合的请求和响应?事实是,放大地图这个操作里会有两次请求/响应过程。请求1:传输框框范围到服务器;响应1:返回放大后的新地图范围给客户端;(客户端的地图控件收到响应1后立即再次发出)请求2:根据新范围问服务器端地图控件所要结果地图图片;响应2:传输地图图片给客户端。一个放大过程才算完成。结论:对Web ADF控件做的一个操作,很可能会引起不只一次请求和响应,但我们只关心且只需要关心最初的请求和最终的结果,因为其他过程Web ADF控件会自动替我们完成。
      任何对Web ADF控件的操作都是和上面的过程一样,客户端的Web ADF Javascript对象发送请求,服务器端相应的控件处理结果并通过“响应”(伴随着一系列对我们透明的后续请求和响应)来将结果同步到客户端去,这个 “响应”就是我们的主角:ADF中的CallbackResult对象(实际上是JSON字符串)。它是由服务器端的Web ADF控件生成,客户端的特定Web ADF Javascript函数负责解析的。不管生成的CallbackResult长什么样,客户端的负责解析的函数名字都叫做:processCallbackResult()。如下图所示:


ADFAJAX2.jpg
      为了逻辑上的需要,比如一个动作对应一个CallbackResult,于是就有了上图的CallbackResultCollection。在服务器端,每个Web ADF Control都有一个Callbackresults属性(CallbackResultCollection),为什么需要这样呢?必须记住一点:“谁请求,谁管理”,即(客户端)发起请求的那个控件(在服务器端所对应的控件)负责传回CallbackResults。CallbackResults传回到客户端后,Web ADF Javascript中的processCallbackResult()函数就会自动对其进行解析。所以开发人员所需要做的工作就是,将服务器端所有产生的CallbackResults交付给负责带回CallbackResults的那个控件——加入控件的CallbackResults属性中。你并不需要关心CallbackResults到了客户端是如何解析的,因为这一切都由processCallbackResult()自动完成。因为请求可能由任何一个控件触发,所以每个Web ADF控件都有一个CallbackResults属性。

      比如,运行时动态给Map控件添加了新的地图服务。在这个过程中,Map控件会发送请求,那么自然是服务器端的Map控件负责将CallbackResults带回客户端。而页面中的TOC控件为了反映出这个变化,在服务器端就要对其进行刷新,刷新这个动作就会使TOC控件自身产生CallbackResults。记住“谁请求,谁管理”,Map请求,Map管理。如果不将TOC刷新的结果通过Map的CallbackResults带回客户端,那么页面上的TOC是不会有任何变化的。正确的做法是:

... <new map resource item added>Toc1.Refresh();Map1.CallbackResults.CopyFrom(Toc1.CallbackResults);

整个过程如下图所示:


ADFAJAX3.jpg
      不是所有的Web ADF变化都需要我们手动处理,下面列出了一些Web ADF控件之间的内在关系:

  • 已经绑定到Map的Toolbar控件上,任何command或tool执行过程中,会自动将Map的CallbackResults拷贝到Toolbar的CallbackResults中。所以如果在自定义command或tool里对Map做了修改,则不需要将其产生的CallbackResults拷贝给负责带回响应的Toolbar控件;
  • 已经绑定到Map的TOC控件,NodeChecked事件会自动将Map产生CallbackResults拷贝给TOC控件;当Map和TOC中包含有可见性依赖比例尺变化而变化的图层时,ScaleChanged事件会将Toc的CallbackResults拷贝给Map控件;
  • ScaleBar绑定到Map后,ScaleBar的PreRender事件会将ScaleBar的CallbackResults拷贝给Map控件;
  • MapCopyrightText控件绑定到Map后,添加或移除map resource item会将MapCopyrightText的CallbackResults拷贝给Map控件;
  • Magnifier绑定到Map后,改变Map的显示范围或初始化tiling scheme时,会将Magnifier的CallbackResults拷贝给Map控件;
  • FloatingPanel的Refresh()方法会将其中所有子控件CallbackResults拷贝到自身的CallbackResults中;
  • TaskResults绑定到Map后,执行一个task会将Map的CallbackResults拷贝给TaskResults控件,TaskResults的CallbackResults随后会自动拷贝给这个执行的task。所以如果你手动将Map的CallbackResults拷贝给这个task的CallbackResults的话,将会产生重复的结果;
  • TaskResults控件有预定义的ContextMenus。这些右键菜单项的ItemClicked事件会将TaskResults和Map的CallbackResults自动拷贝给ContextMenu。
          这些内在关系在下面的Web ADF控件一览图中可见一斑。除了以上事件,其他情况下的CallbackResults都需要你自己将其添加到负责带回响应的控件中。


    ADFAJAX4.jpg
          另外,还可以在服务器端自定义CallbackResult,来达到修改客户端非Web ADF控件或者执行一段Javascript代码的目的。自定义CallbackResult可已通过其构造函数或静态方法来实现,这里不再赘述:


    ADFAJAX5.jpg
          当然,别忘了将自己创建的CallbackResults拷贝到正确的控件中。
          至此,如何正确使用CallbackResult的问题已经讲完了,这一切叫做Web ADF中callback result框架。它给我们提供了统一的编程体验,而你则可以根据自己的需要和编程经验来选择使用Client Callback还是ASP.NET AJAX。最后郑重提醒一点:如果使用了ASP.NET AJAX方式,在页面中强烈建议让所有Web ADF控件远离UpdatePanel。因为它们本身就能够注册到ScriptManager中,在完全刷新自己的情况下即可完成必要的内容更新。如果你将Web ADF控件放在UpdatePanel中,虽然能够正常工作,但刷新整个Web ADF控件。
    第三部分:深入理解Web ADF中的AJAX
          现在来看看前面提出的问题:Web ADF究竟是如何利用ASP.NET中的Client Callback或是ASP.NET AJAX的?首先明确一点,Web ADF控件只能使用一种异步通信方式,要么是Client Callback,要么是ASP.NET AJAX。如果页面中有ScriptManager控件,就意味着你选择了ASP.NET AJAX方式进行异步通信,那么Web ADF控件的实际运行中也会采用这种方式;反之如果页面中没有ScriptManager控件,那么Web ADF实质上会采用Client Callback方式来进行异步通信。下面分别通过几个场景来看看两种方式的实际操作区别:
          如果采用了Client Callback方式:
    • 场景一:Web ADF控件来产生请求和响应,客户端processCallbackResult()来处理响应
            这个过程就是第二部分中假设的几个场景,请求由一个Web ADF控件产生,服务器端进行处理,也可在此时创建自定义CallbackResult来对页面中其他控件进行修改,最后将这些CallbackResults赋给正确的控件即可;响应带回客户端后,由Web ADF Javascript的processCallbackResult()函数进行解析。如图所示:


      ADFAJAX6.jpg
      • 场景二:非Web ADF控件来产生请求和响应,客户端processCallbackResult()来处理响应
              比如页面实现了ICallbackEventHandler接口,请求由页面中的一个HTML Button发出,在RaiseCallbackEvent()方法中将所有响应,也包括非Web ADF控件的,都构建成CallbackResult的结构,并集中到CallbackResultCollection中,然后在GetCallbackResult()方法中返回CallbackResultCollection.ToString(),客户端处理callback响应的js函数指定为Web ADF Javascript中的processCallbackResult()函数,则省去了自己解析callback响应的麻烦。如图所示:


        ADFAJAX7.jpg
        • 场景三:非Web ADF控件来产生请求和响应,客户端自己处理响应
                除了在客户端使用自定义的函数来解析包括CallbackResult结构在内的服务器响应外,其余和场景二是相同的。这种情况下你需要知道CallbackResult的JSON字符串中,使用“:::”分隔符来分隔参数,使用“^^^”分隔符来分隔多个CallbackResult,此外processCallbackResult()的定义可在X:\Inetpub\wwwroot\aspnet_client\ESRI\WebADF\JavaScript\ESRI.ADF.System.debug.js中找到。除非你已经有一套处理callback响应的js框架,否则还是推荐在客户端使用processCallbackResult()来进行响应处理。如图所示:


          ADFAJAX8.jpg
                如果采用了ASP.NET AJAX方式:
          • 场景一:Web ADF控件触发partial postback
                  此情形和Client Callback方式场景一类似,发送请求的Web ADF控件是ASP.NET AJAX中的trigger,它的CallbackResults注册为ScriptManager的一个data item返回客户端。如下图所示:


            ADFAJAX9.jpg
            • 场景二:非Web ADF控件触发partial postback
                    trigger是非Web ADF控件时,更新Web ADF控件内容的方法有三种:UpdatePanels,data items或dynamic script blocks。如果将Web ADF控件放入UpdatePanel,则根据UpdatePanel的特性,会刷新里面整个Web ADF控件,这是不必要的,Web ADF控件所需要更新的内容仅仅是服务器端产生的CallbackResults,由客户端的processCallbackResult()即可完成,而不需要从头到尾的洗心革面。所以CallbackResults可以当做data items或dynamic script blocks来插入到partial postback的响应中。
                    作为dynamic script blocks时:首先要使用dynamic script blocks,页面中至少要有一个UpdatePanel(空的也可)。只需要在客户端调用ESRI.ADF.System.processCallbackResult() 来处理服务器端产生的CallbackResults(作为该函数的参数),如下:

              . . .string jsProcessCallbackResult = string.Format("ESRI.ADF.System.processCallbackResult('{0}');",    Map1.CallbackResults.ToString().Replace("\\", "\\\\"));ScriptManager.RegisterClientScriptBlock(Page, sender.GetType(), "changeextent",     string.Format(jsProcessCallbackResult, Map1.CallbackResults), true);

              此方式的优点:Web ADF控件不需要完全刷新,客户端响应解析也不需要自己来写;缺点:注册的dynamic script blocks会保存在客户端内存中,最终可能会导致浏览器内存超出限制。
                    作为data items时:ScriptManager中注册的data items可以在服务器上以(JSON)字符串形式打包传到客户端进行处理。客户端接收到服务器端的异步postback响应后,但在进行任何局部内容的修改前,会触发客户端的pageLoading事件。这样就可以利用PageRequestManager捕捉到这个事件,从而将注册成data items的CallbackResults交给processCallbackResult() 来处理。过程如下图所示:


              ADFAJAX10.jpg
                    在服务器端将CallbackResult注册成data item:

              ScriptManager1.RegisterDataItem(Page, Map1.CallbackResults.ToString(), false);
              监听pageLoading事件:

              <script>                                Sys.Application.add_init(onInitFunction);                                    // Called once during application initializationfunction onInitFunction(){   Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(AsyncResponseHandler);}// Called whenever a response to a partial postback is processed on the clientfunction AsyncResponseHandler(sender, args) {   var dataItems = args.get_dataItems();   if (dataItems['__Page'] != null)      ESRI.ADF.System.processCallbackResult(dataItems['__Page']);}</script>        



              原创粉丝点击