Hello World
Spiga

深入Atlas系列:客户端网络访问基础结构(上) - WebRequest的工作流程与生命周期

2006-10-27 00:50 by 老赵, 4291 visits
  ASP.NET AJAX的许多功能会要求异步地访问服务器端,例如访问Web Services,Authentication/Profile Services(事实上和访问Web Services是相同的机制)和Partial Rendering。在ASP.NET AJAX中,所有的这些访问都是通过一个网络访问的基础结构来完成的,无一例外。

我们话不多说,直接切入正题。先来看一下官方网站上Client Architecture的图示:


可以在图的左边中间部分可以看到ASP.NET AJAX客户端进行网络访问的基础结构和相关的类。事实上,除了Browser Compatibility Laryer外,WebRequestManager,WebRequest和WebRequestExecutor的子类(在默认情况下则是XMLHttpExecutor,封装了XMLHttpRequest对象)组成了客户端的Network Communication Layer。无论是Web Services Proxy,还是Role/Authentication/Profile Services都是通过它们访问的。

一般来说,ASP.NET AJAX提供的类已经足够大多数应用的需求,但是在某些特殊情况,可能需要做一些改变。因此,Network Communication Layer提供了一定程度上的扩展能力——通过提供自定义的WebRequestExecutor子类来替换默认设置:XMLHttpExecutor。这种改变能够应用于一个单独的WebRequest,也可以对全局进行一个设置。这个我们就能对于客户端的行为做一些简单的修改和补充。这样可以说是Network Communication Layer唯一的“官方”扩展点,其实能力有限。至于使用一些灵活的办法从JavaScript语言特性能上进行的修改,在这里就忽略不计了。

这样我们就能对于客户端的行为做一些简单的修改和补充。那么如果对其进行大量的修改会怎么样?如果这种修改的确能够满足您的需要,当然可以。可是这样的话,Partial Rendering就很可能无法实现了。Partial Rendering的作法是在客户端和服务器端产生一个严谨的协议,用来传递和处理大量的数据。如果有任何改变被加诸于这些数据上,协议就被破坏了,这个可以说是ASP.NET AJAX的最重要的功能之一的控件也就失效了。

当然,在自定义WebRequestExecutor时,我们可以在实现内部检查WebRequest是否正准备进行Partial Rendering的操作,如果是,则将功能委托给XMLHttpExecutor,否则就使用自己的扩展。事实上,ASP.NET AJAX的服务器端为了提供客户端访问Web Services的能力,也是使用了类似的方法扩展ASP.NET 2.0对于Web Services请求的处理(详情请见《深入Atlas系列:Web Sevices Access in Atlas(2) - 服务器端支持(上)》。虽然部分内容已经过期,但是其基本原理并没有改变,我会在“深入Atlas系列”的后续文章中进行CTP与RTM之间的比对)。

我们先来看一幅顺序图,大致了解一下三个对象在工作时的协作方式,再来具体分析一下从一个Request被建立到获得回复的详细过程。另外,对于Network Communication Layer的扩展点以及其默认实现——WebRequestExecutor和XMLHttpExecutor,我们在下一篇文章里将看一下它们的代码,以获得开发一个自定义WebRequestExecutor的具体认识。


上面是从User建立一个WebRequest开始,到得到Response的整个过程(身边只有Visio可用,却不知道怎么画SD,就随便画了一下,很有问题,但是大家可以先理解它的意思,以后我会重新画的)。这个就是客户端发起异步调用的最常见过程(当然,隐藏了几乎所有的事件,这些事件会在稍后进行描述)。因此,我想可以通过这幅图来大概了解一下客户端和服务器端是如何交互的。

接下来,我们详细了解一下这整个流程的每个步骤。

一、创建Sys.Net.WebRequest

这是发起一个服务器请求的第一步。首先建立一个Sys.Net.WebRequest对象,然后通过addHeader,set_url等方法设置这个请求的各个信息,然后调用add_complete方法来注册complete事件。请注意,这和CTP版本的Sys.Net.WebRequest不同。在CTP版本的Sys.Net.WebRequest中,存在着三个事件:complete,timeout和aborted,用户可以有选择地注册这些事件。在RTM的Sys.Net.WebRequest中,三个事件被合成了一个complete事件。不管这个Request的结果如何,都会在这个触发这个事件,用户需要在响应这个事件的方法里自行根据得到的Response的信息来分辨这个请求的状况。具体的判断方式稍后再进行说明。在创建完WebRequest对象后,将调用它的invoke方法执行这个请求。一个WebRequest对象只能被invoke一次。


二、将WebRequest对象交给WebRequestManager执行

在WebRequest对象的invoke方法被调用以后,它会调用“Sys.Net.WebRequestManager.executeRequest(this)”将自身交给全局的WebRequestManager执行。这里的WebRequestManager其实是Sys.Net._WebRequestManager类的实例。在初始化环境的时候,就会将这个类实例化一个对象,赋值到Sys.Net.WebRequestManager变量中,以一个Singleton的形象供别的方法调用。它会在处理Request的过程中在特定的时刻触发一些事件,用户可以依靠响应这些事件来改变Request的行为,例如阻止特定的Request,或者改变Request的一些属性等等。

乍看下来,RTM版本中的WebRequestManager和CTP中的WebRequestManager没有很大的区别嘛(引入了几个事件除外)。事实上,个人认为在这个方面Atlas打了自己一个耳光。在CTP版本的WebRequestManager中,对于管理WebRequest有一套比较复杂的方法,其理由是能够更好地利用好浏览器的资源,以提高WebRequest的效率。这就是Atlas对外一直声称的“Client Request Stack”,这一点被当作Atlas的重要特点来看待。不过这一点在RTM版本中被取消了,新的WebRequestManager在处理一个WebRequest对象的时候仅仅是触发事件,然后简单地使用Executor来调用这个WebRequest对象。

下面列举了调用了executeRequest方法后的关键逻辑:
  1. 检查WebRequest是否指定了WebRequestExecutor(这可以在构造WebRequest的时候指定)。
  2. 如果WebRequest没有指定WebRequestExecutor,则使用set_executor方法分配默认的WebRequestExecutor
  3. 构造Sys.Net.NetworkRequestEventArgs参数对象,触发WebRequestManager的invokingRequest事件。
  4. 如果Sys.Net.NetworkRequestEventArgs对象的cancel属性为true,取消执行Request。
  5. 调用executor对象的executeRequest方法,以执行Request。

三、WebRequestExecutor执行Request

在这里,以ASP.NET AJAX的默认WebRequestExecutor类:XMLHttpExecutor为例进行说明。在XMLHttpExecutor的executeRequest方法被调用后,XMLHttpExecutor会构造一个XMLHttpRequest对象,并根据WebRequest的各项属性设定XMLHttpRequest的对象,并指定XMLHttpRequest对象的onreadystatechange为自身的私有方法_onReadyStateChange。调用了executeRequest方法后的关键逻辑如下:
  1. 构造XMLHttpRequest对象,并根据WebRequest对象的属性设定它的各项属性。
  2. 使用window.setTimeout用于监听超时发生。
  3. 调用XMLHttpRequest的send方法。
  在timeout的响应方法中调用XMLHttpRequest的abort方法,并调用WebRequest的complete方法。在_onReadyStateChange方法中清除监听timeout的Timer,并调用complete方法。当然,这只是个简单的描述,事实上还需要对Executor的属性进行设定。我们将在下篇文章中将对此进行详细讨论。在complete方法被调用后关键逻辑如下:
  1. 触发WebRequestManager的completedRequest事件。
  2. 触发WebRequest的complete事件。

四、用户响应WebRequest的complete事件

在响应WebRequest的complete事件时,需要对于所获得的结果进行判断,以确定这个Request的结果到底如何,是成功了,还是出错了,亦或是过期了?需要注意的是,我们虽然监听的是WebRequest对象的事件,但是回调函数的第一个参数是WebRequestExecutor对象!executor对象在这里事实上应该被看作是一个response。我们就来简单看一下应该如何根据executor对象的属性来判断Request的结果吧。为了对于这部分逻辑有简单而清晰的描述,我就使用代码片断来说明吧。代码框架如下:

function onComplete(response, eventArgs)
{
    
if (response.get_aborted())
    {
        
// Abort
    }
    
else if (response.get_responseAvailable())
    {
        
var statusCode = response.get_statusCode();

        
if (((statusCode < 200|| (statusCode >= 300)))
        {
            
// Error
        }
        
else
        {
            
// Success
        }
    }
    
else
    {
        
if (response.get_timedOut())
        {
            
// Timeout
        }
        
else
        {
            
// Error
        }
    }
}

这就是判断一个Request结果的逻辑框架了,非常清晰。阅读过ASP.NET AJAX客户端代码的朋友们可以发现,在RTM版本中这段逻辑不只一次出现过。我们如果需要直接使用WebRequest对象时,也应该使用这个逻辑进行判断。


到这里,我们应该已经搞清楚了从一个WebRequest对象被构造出来以后,是如何通过WebRequestManager和WebRequestExecutor而执行的,之间会触发哪些事件,而最后又是如何通过Response(WebRequestExecutor)对象来获得Request的结果。在下一篇文章中,我们将通过分析WebRequestExecutor、XMLHttpExecutor以及相关类的实现,来了解应该如何自定义和使用一个WebRequestExecutor。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

16 条回复

  1. jailu
    *.*.*.*
    链接

    jailu 2006-10-27 01:17:00

    占个位先

  2. 老赵
    admin
    链接

    老赵 2006-10-27 01:20:00

    @jailu
    :)

  3. cathsfz
    *.*.*.*
    链接

    cathsfz 2006-10-27 02:31:00

    onComplete中,除了Success、Error、Timeout,还有Abort呢?

  4. 老赵
    admin
    链接

    老赵 2006-10-27 03:01:00

    @cathsfz
    abort是WebRequestExecutor的方法,被调用之后就不会触发complete事件了,因此onComplete方法不会对其进行判断。
    在XMLHttpExecutor中,如果调用了abort方法,XMLHttpRequest还没有返回,那么就会摘除onreadystatechange回调函数,并调用XMLHttpRequest的abort方法,还会把timeout监听给取消掉。:)

  5. 李.net
    *.*.*.*
    链接

    李.net 2006-10-27 10:15:00

    什么时候能有具体的实例来看,这个理论性太强了,只能是看一次有一个印象,但还是比较抽象,不知道从哪里下手

  6. cathsfz
    *.*.*.*
    链接

    cathsfz 2006-10-27 10:15:00

    @Jeffrey Zhao
    CTP确实是有4个callback的,分别是onComplete, onTimeout, onError, onAborted。而这4个callback可以放在一个configuration参数中传递给调用代理,结构为:
    {
    onMethodComplete: onComplete,
    onMethodTimeout: onTimeout,
    onMethodError: onError,
    onMethodAborted: onAborted,
    userContext: context,
    timeoutInterval: timeout
    }
    其中,onComplete和onError的参数列表是(result, response, userContext),而onTimeout和onAborted的参数列表是(request, userContext)。

  7. 老赵
    admin
    链接

    老赵 2006-10-27 10:40:00

    @cathsfz
    您说的没错,这些我以前在“深入Atlas系列”中详细谈到过的。
    我在onComplete方法中遗漏了这点,已经补上。:)

  8. 老赵
    admin
    链接

    老赵 2006-10-27 10:50:00

    @李.net
    这个是“分析”部分,以后会有“示例”部分的。:)

  9. 小蜗牛
    *.*.*.*
    链接

    小蜗牛 2006-10-27 14:41:00

    满到位的分析。恩,还有就是觉得你配色配的也不错。:)

  10. 刚刚
    *.*.*.*
    链接

    刚刚 2006-10-27 16:40:00

    顶个位置先。今天比较忙,收藏后慢慢看!

  11. 老赵
    admin
    链接

    老赵 2006-10-27 16:56:00

    @刚刚
    :)

  12. cathsfz
    *.*.*.*
    链接

    cathsfz 2006-10-29 01:34:00

    关于userContext,现在官方文档上说明那用于传递一个String,但我记得以前是可以传任何Object的,现在是不是限制为仅能传递String了?

    另外,期待本系列的下一篇。

  13. 老赵
    admin
    链接

    老赵 2006-10-29 02:08:00

    @cathsfz
    userContext依旧可以是任何对象,事实上在客户端这些类并没有用到这个值。这个值还是为开发人员保留的。不过这个现在这个东西的存在让人觉得有点好笑,userContext对象已经不会被传入任何回调函数了,得到的方式是例如在onComplete方法中使用response.get_webRequest().get_userContext(),我觉得有点汗。用这个方式,只是为了在WebRequest对象上保留一个附加对象吗?毕竟我们用的是JavaScript。如果是因为客户端代码要塑造一个“完整”的模型的话……这样的功能也颇为无聊了。另外真要模型的话,为什么WebRequest的complete事件的第一个参数是WebRequestExecutor呢?

    下一片文章正在写,不过感觉会蛮无聊的,哎,那些代码其实很简单……

  14. 蛙蛙池塘
    *.*.*.*
    链接

    蛙蛙池塘 2006-11-30 22:25:00

    第二篇依然云里雾里的

  15. 老赵
    admin
    链接

    老赵 2006-11-30 22:33:00

    @蛙蛙池塘
    别急,这里每篇我都用了2-3个小时,如果不是照着代码看的确不容易理解。:)

  16. gjh[未注册用户]
    *.*.*.*
    链接

    gjh[未注册用户] 2007-06-29 16:41:00

    学习

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我