经常在论坛上看到很多朋友问:为什么我已经创建了一个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是不会有任何变化的。正确的做法是:
CODE:
... <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。
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()来处理响应
ADFAJAX6.jpg
- 场景二:非Web ADF控件来产生请求和响应,客户端processCallbackResult()来处理响应
ADFAJAX7.jpg
- 场景三:非Web ADF控件来产生请求和响应,客户端自己处理响应
ADFAJAX8.jpg
如果采用了ASP.NET AJAX方式:
- 场景一:Web ADF控件触发partial postback
ADFAJAX9.jpg
- 场景二:非Web ADF控件触发partial postback
作为dynamic script blocks时:首先要使用dynamic script blocks,页面中至少要有一个UpdatePanel(空的也可)。只需要在客户端调用ESRI.ADF.System.processCallbackResult() 来处理服务器端产生的CallbackResults(作为该函数的参数),如下:
CODE:
. . .
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:
CODE:
ScriptManager1.RegisterDataItem(Page, Map1.CallbackResults.ToString(), false);
监听pageLoading事件:
CODE:
<script>
Sys.Application.add_init(onInitFunction);
// Called once during application initialization
function onInitFunction()
{
Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(AsyncResponseHandler);
}
// Called whenever a response to a partial postback is processed on the client
function AsyncResponseHandler(sender, args)
{
var dataItems = args.get_dataItems();
if (dataItems['__Page'] != null)
ESRI.ADF.System.processCallbackResult(dataItems['__Page']);
}
</script>
现在你对.NET ADF中的AJAX还有疑问吗?如果有不要犹豫,请继续阅读以下学习资源,之后欢迎进行交流。论坛中S-H-G朋友的系列教程:
ArcGIS Server9.3 AJAX系列(一)之CallbackResults
ArcGIS Server9.3 AJAX系列(二)之Client CallBack解决方案
ArcGIS Server9.3 AJAX系列(三)之ASP.NET AJAX解决方案
ArcGIS Server9.3 AJAX系列(四)之总结
论坛中Jueery版主的系列教程:
ArcGIS Server .Net ADF中的AJAX(一)(帖子丢失,可在社区3周年精华文集中找到)
ArcGIS Server .Net ADF中的AJAX(二)
ArcGIS Server .Net ADF中的AJAX(二)续
ArcGIS Server .Net ADF中的AJAX(三)
关于Client Callback的详解:
carlbiao朋友的新手讲解.net的callback机制
ASP.NET 2.0's Client Callback Feature
关于ASP.NET AJAX:
AJAX : The Official Microsoft ASP.NET Site
关于XMLHttpRequest:
http://www.w3school.com.cn/php/php_ajax_xmlhttprequest.asp
最后不能少的就是帮AGS助中的文章,一切细节都在其中:
AJAX and ASP.NET
AJAX Capabilities of Web ADF Controls Overview
Working with CallbackResults
ASP.NET Callback Solutions
ASP.NET AJAX partial postback solutions