深入Atlas系列:客户端网络访问基础结构示例(1) - 编写并使用自定义的WebRequestExecutor
2016-07-29 00:00
716 查看
WebRequestExecutor是ASP.NET AJAX网络访问基础结构的唯一修改点。理论上,我们可以使用自定义的WebRequestExecutor来取代默认的XMLHttpExecutor。我们要做的,其实只是开发一个继承于Sys.Net.WebRequestExecutor类。不过事实上,在实际使用中,Sys.Net.XMLHttpExecutor已经足够用了,真的要自定义,也只需继承这个类即可。就像接下去的例子一样。
可是,Sys.Net.XMLHttpExecutor作为一个父类来说,可以说相当的不友好。先不多说,我们开始吧。在这个例子中,我们将开发一个TraceExecutor,如果使用了这个Executor,Request和Response的信息都会被打印在页面上,开发人员可以把这些内容作为一个参考。TraceExecutor非常简单,代码也很短。
首先,是构造函数代码。如下:
Jeffz.Net.TraceExecutor
=
function
Jeffz$Net$TraceExecutor()
{
Jeffz.Net.TraceExecutor.initializeBase(
this
);
}
一般情况下,在这个方法中,会初始化所有的“私有”变量。不过在这里,我们不需要。
然后,我们将覆盖XMLHttpExecutor的executeRequest方法,我们需要作的,就是将WebRequest对象打印在页面上。代码如下:
function
Jeffz$Net$TraceExecutor$executeRequest()
{
debug.trace(
"
<b>Request sent at
"
+
new
Date()
+
"
:</b>
"
);
debug.dump(
this
.get_webRequest(),
"
Request
"
);
Jeffz.Net.TraceExecutor.callBaseMethod(
this
,
"
executeRequest
"
);
}
我在这里使用Debug模式下,为方法起了一个“别名”,在prototype中需要使用一下。自然,我们也需要调用registerClass,表示类的继承关系。代码如下:
Jeffz.Net.TraceExecutor.prototype
=
{
executeRequest : Jeffz$Net$TraceExecutor$executeRequest
}
Jeffz.Net.TraceExecutor.registerClass('Jeffz.Net.TraceExecutor', Sys.Net.XMLHttpExecutor);
等一下,为什么我们就覆盖executeRequest这一个方法呢?很显然,我们只是打印出了Request的信息,那么Response呢?我们是不是还需要再覆盖另一个方法呢?
我们的确应该再写一些代码,用来将Response信息打印出来,但是我们的做法不是覆盖XMLHttpExecutor的另一个方法,原因有两点:
XMLHttpExecute作为一个父类,相当地不友好。可以从代码里看到,它用来相应XMLHttpRequest对象onreadystatechanged的回调函数“_onReadyStateChanged”是定义在其构造函数内。也就是说,它又使用了Closure,而不是prototype。这样我们事实上就无法覆盖那些方法了,如果要继续在TraceExecutor上面做文章的话,我们必须写大量的代码,这几乎就相当于重新写了一个WebRequestExecutor。为什么会出现这种情况?是XMLHttpExecutor故意写成这个样的吗?我们来看一下:在XMLHttpRequest代码的executeRequest方法中,设置XMLHttpRequest对象onreadystatechanged的代码是:“this._xmlHttpRequest.onreadystatechanged = this._onReadyStateChanged”,然后在_onReayStateChanged方法中使用了保留在Closure之中的“_this”变量。这个是没有“Function.createDelegate”时,或者在使用Closure时的写法,编写过Windows Live Gadget的朋友们可以回忆一下,在使用官方提供的模版时,是不是在一开始也保留了一个“m_this”变量?这是相似的原因。那么在XMLHttpExecutor里,为什么不使用“Function.createDelegate”方法?估计只有负责这部分代码的开发人员才知道了。因此,我认为这里部分使用了Closure不是故意所为,这更像是负责这段代码的开发人员一时“神志错乱”。
即使我们覆盖了“_onReadyStateChanged”方法,我们还漏了一些别的。如果这个Request超时了呢?如果这个Request被abort了呢?如果我们一一覆盖这些方法的话,似乎也有些太麻烦了。
因为上面两个原因(主要是第一个),我们将响应Sys.Net.WebRequestManager.completeRequest事件,在这个事件被触发时打印Response的信息。代码如下:
Jeffz.Net.TraceExecutor.traceResponse
=
function
Jeffz$Net$TraceExecutor$traceResponse(response)
{
debug.trace(
"
<b>Response received at
"
+
new
Date()
+
"
:</b>
"
);
debug.dump(response,
"
Response
"
);
}
Sys.Net.WebRequestManager.add_completedRequest(Jeffz.Net.TraceExecutor.traceResponse);
我们可以尝试着使用一下,代码很简单,将HTML和Javascript一并贴出了。代码如下:
<
asp:ScriptManager
runat
="server"
ID
="ScriptManager1"
>
<
Scripts
>
<
asp:ScriptReference Path
=
"
Debug.js
"
/>
<
asp:ScriptReference Path
=
"
TraceExecutor.js
"
/>
</
Scripts
>
</
asp:ScriptManager
>
<
asp:UpdatePanel
runat
="server"
ID
="UpdatePanel1"
>
<
Triggers
>
<
asp:AsyncPostBackTrigger
ControlID
="btnRefresh"
/>
</
Triggers
>
<
ContentTemplate
>
<%
=
DateTime.Now.ToString(
"
r
"
)
%>
</
ContentTemplate
>
</
asp:UpdatePanel
>
<
asp:Button
runat
="server"
ID
="btnRefresh"
Text
="Refresh"
/>
<
script
language
="javascript"
>
Sys.Net.WebRequestManager.set_defaultExecutorType(
"
Jeffz.Net.TraceExecutor
"
);
</
script
>
在这里,需要使用我在《为ASP.NET AJAX 1.0 Beta补充trace和dump功能》里提到的Debug.js来补充ASP.NET AJAX中缺少的trace和dump功能。在代码的最后会使用Javascript将默认的WebRequestExecutor设为Jeffz.Net.TraceExecutor。
打开页面,点击Refresh按钮,嗯?Request信息不错,不过Response信息为什么只是这些?
Response {Jeffz.Net.TraceExecutor}
+_webRequest {Sys.Net.WebRequest}
+_resultObject: null
+_xmlHttpRequest {Object}
+_responseAvailable: true
+_timedOut: false
+_timer: null
+_aborted: false
+_started: true
原因在于,由于缺少了了Type Descriptor,debug.dump方法只会将该对象上附属的“值”(例如:this._started)打印出来,而不会调用任何方法。而在XMLHttpExecutor中,许许多多方法(各种属性的get方法)是直接将消息委托给XMLHttpRequest对象,而不是将信息保留在XMLHttpExecutor附属“值”上,因此,我们得到的只能是这些无用的信息了。
那么我们该怎么做?那么就为Response对象补充一个Type Descriptor。这里的Response对象其实就是Jeffz.Net.TraceExecutor类的实例,因此我们需要为该类添加一个Type Descriptor。代码如下:
Jeffz.Net.TraceExecutor.descriptor
=
{
properties:
[
{name: 'started', type: Boolean },
{name: 'timedOut', type: Boolean },
{name: 'aborted', type: Boolean },
{name: 'responseAvailable', type: Boolean },
{name: 'responseData', type: String },
{name: 'statusCode', type: Number },
{name: 'statusText', type: String }
]
}
这是RTM中Type Descriptor的表示方式。可以发现我在这里只添加了部分,而不是全部的属性。例如缺少了object属性,为什么呢?在XMLHttpExecutor中get_object方法是这样定义的:
function
Sys$Net$WebRequestExecutor$get_object() {
//
/ <value></value>
if
(arguments.length
!==
0
)
throw
Error.parameterCount();
if
(
!
this
._resultObject) {
this
._resultObject
=
Sys.Serialization.JavaScriptSerializer.deserialize(
this
.get_responseData());
}
return
this
._resultObject;
}
我们可以发现,这段逻辑并没有判断responseData的合法性,如果它不是一个正确的JSON字符串,就会抛出异常。如果一定要在Type Descriptor中定义object属性的话,那么我们还必须在TraceExecutor中覆盖XMLHttpRequest的object属性。作为一个没有多少使用价值的演示,我们就不作得如此“仁至义尽”了。:)
现在我们再来看一下打印出来的内容:
点击这里下载示例代码。
可是,Sys.Net.XMLHttpExecutor作为一个父类来说,可以说相当的不友好。先不多说,我们开始吧。在这个例子中,我们将开发一个TraceExecutor,如果使用了这个Executor,Request和Response的信息都会被打印在页面上,开发人员可以把这些内容作为一个参考。TraceExecutor非常简单,代码也很短。
首先,是构造函数代码。如下:
Jeffz.Net.TraceExecutor
=
function
Jeffz$Net$TraceExecutor()
{
Jeffz.Net.TraceExecutor.initializeBase(
this
);
}
一般情况下,在这个方法中,会初始化所有的“私有”变量。不过在这里,我们不需要。
然后,我们将覆盖XMLHttpExecutor的executeRequest方法,我们需要作的,就是将WebRequest对象打印在页面上。代码如下:
function
Jeffz$Net$TraceExecutor$executeRequest()
{
debug.trace(
"
<b>Request sent at
"
+
new
Date()
+
"
:</b>
"
);
debug.dump(
this
.get_webRequest(),
"
Request
"
);
Jeffz.Net.TraceExecutor.callBaseMethod(
this
,
"
executeRequest
"
);
}
我在这里使用Debug模式下,为方法起了一个“别名”,在prototype中需要使用一下。自然,我们也需要调用registerClass,表示类的继承关系。代码如下:
Jeffz.Net.TraceExecutor.prototype
=
{
executeRequest : Jeffz$Net$TraceExecutor$executeRequest
}
Jeffz.Net.TraceExecutor.registerClass('Jeffz.Net.TraceExecutor', Sys.Net.XMLHttpExecutor);
等一下,为什么我们就覆盖executeRequest这一个方法呢?很显然,我们只是打印出了Request的信息,那么Response呢?我们是不是还需要再覆盖另一个方法呢?
我们的确应该再写一些代码,用来将Response信息打印出来,但是我们的做法不是覆盖XMLHttpExecutor的另一个方法,原因有两点:
XMLHttpExecute作为一个父类,相当地不友好。可以从代码里看到,它用来相应XMLHttpRequest对象onreadystatechanged的回调函数“_onReadyStateChanged”是定义在其构造函数内。也就是说,它又使用了Closure,而不是prototype。这样我们事实上就无法覆盖那些方法了,如果要继续在TraceExecutor上面做文章的话,我们必须写大量的代码,这几乎就相当于重新写了一个WebRequestExecutor。为什么会出现这种情况?是XMLHttpExecutor故意写成这个样的吗?我们来看一下:在XMLHttpRequest代码的executeRequest方法中,设置XMLHttpRequest对象onreadystatechanged的代码是:“this._xmlHttpRequest.onreadystatechanged = this._onReadyStateChanged”,然后在_onReayStateChanged方法中使用了保留在Closure之中的“_this”变量。这个是没有“Function.createDelegate”时,或者在使用Closure时的写法,编写过Windows Live Gadget的朋友们可以回忆一下,在使用官方提供的模版时,是不是在一开始也保留了一个“m_this”变量?这是相似的原因。那么在XMLHttpExecutor里,为什么不使用“Function.createDelegate”方法?估计只有负责这部分代码的开发人员才知道了。因此,我认为这里部分使用了Closure不是故意所为,这更像是负责这段代码的开发人员一时“神志错乱”。
即使我们覆盖了“_onReadyStateChanged”方法,我们还漏了一些别的。如果这个Request超时了呢?如果这个Request被abort了呢?如果我们一一覆盖这些方法的话,似乎也有些太麻烦了。
因为上面两个原因(主要是第一个),我们将响应Sys.Net.WebRequestManager.completeRequest事件,在这个事件被触发时打印Response的信息。代码如下:
Jeffz.Net.TraceExecutor.traceResponse
=
function
Jeffz$Net$TraceExecutor$traceResponse(response)
{
debug.trace(
"
<b>Response received at
"
+
new
Date()
+
"
:</b>
"
);
debug.dump(response,
"
Response
"
);
}
Sys.Net.WebRequestManager.add_completedRequest(Jeffz.Net.TraceExecutor.traceResponse);
我们可以尝试着使用一下,代码很简单,将HTML和Javascript一并贴出了。代码如下:
<
asp:ScriptManager
runat
="server"
ID
="ScriptManager1"
>
<
Scripts
>
<
asp:ScriptReference Path
=
"
Debug.js
"
/>
<
asp:ScriptReference Path
=
"
TraceExecutor.js
"
/>
</
Scripts
>
</
asp:ScriptManager
>
<
asp:UpdatePanel
runat
="server"
ID
="UpdatePanel1"
>
<
Triggers
>
<
asp:AsyncPostBackTrigger
ControlID
="btnRefresh"
/>
</
Triggers
>
<
ContentTemplate
>
<%
=
DateTime.Now.ToString(
"
r
"
)
%>
</
ContentTemplate
>
</
asp:UpdatePanel
>
<
asp:Button
runat
="server"
ID
="btnRefresh"
Text
="Refresh"
/>
<
script
language
="javascript"
>
Sys.Net.WebRequestManager.set_defaultExecutorType(
"
Jeffz.Net.TraceExecutor
"
);
</
script
>
在这里,需要使用我在《为ASP.NET AJAX 1.0 Beta补充trace和dump功能》里提到的Debug.js来补充ASP.NET AJAX中缺少的trace和dump功能。在代码的最后会使用Javascript将默认的WebRequestExecutor设为Jeffz.Net.TraceExecutor。
打开页面,点击Refresh按钮,嗯?Request信息不错,不过Response信息为什么只是这些?
Response {Jeffz.Net.TraceExecutor}
+_webRequest {Sys.Net.WebRequest}
+_resultObject: null
+_xmlHttpRequest {Object}
+_responseAvailable: true
+_timedOut: false
+_timer: null
+_aborted: false
+_started: true
原因在于,由于缺少了了Type Descriptor,debug.dump方法只会将该对象上附属的“值”(例如:this._started)打印出来,而不会调用任何方法。而在XMLHttpExecutor中,许许多多方法(各种属性的get方法)是直接将消息委托给XMLHttpRequest对象,而不是将信息保留在XMLHttpExecutor附属“值”上,因此,我们得到的只能是这些无用的信息了。
那么我们该怎么做?那么就为Response对象补充一个Type Descriptor。这里的Response对象其实就是Jeffz.Net.TraceExecutor类的实例,因此我们需要为该类添加一个Type Descriptor。代码如下:
Jeffz.Net.TraceExecutor.descriptor
=
{
properties:
[
{name: 'started', type: Boolean },
{name: 'timedOut', type: Boolean },
{name: 'aborted', type: Boolean },
{name: 'responseAvailable', type: Boolean },
{name: 'responseData', type: String },
{name: 'statusCode', type: Number },
{name: 'statusText', type: String }
]
}
这是RTM中Type Descriptor的表示方式。可以发现我在这里只添加了部分,而不是全部的属性。例如缺少了object属性,为什么呢?在XMLHttpExecutor中get_object方法是这样定义的:
function
Sys$Net$WebRequestExecutor$get_object() {
//
/ <value></value>
if
(arguments.length
!==
0
)
throw
Error.parameterCount();
if
(
!
this
._resultObject) {
this
._resultObject
=
Sys.Serialization.JavaScriptSerializer.deserialize(
this
.get_responseData());
}
return
this
._resultObject;
}
我们可以发现,这段逻辑并没有判断responseData的合法性,如果它不是一个正确的JSON字符串,就会抛出异常。如果一定要在Type Descriptor中定义object属性的话,那么我们还必须在TraceExecutor中覆盖XMLHttpRequest的object属性。作为一个没有多少使用价值的演示,我们就不作得如此“仁至义尽”了。:)
现在我们再来看一下打印出来的内容:
点击这里下载示例代码。
相关文章推荐
- 深入Atlas系列:客户端网络访问基础结构示例(1) - 编写并使用自定义的WebRequestExecutor
- 深入Atlas系列:客户端网络访问基础结构示例(1) - 编写并使用自定义的WebRequestExecutor
- 深入Atlas系列:客户端网络访问基础结构(下) - WebRequestExecutor和XMLHttpExecutor
- 深入Atlas系列:客户端网络访问基础结构(下) - WebRequestExecutor和XMLHttpExecutor
- 深入Atlas系列:客户端网络访问基础结构(下) - WebRequestExecutor和XMLHttpExecutor
- 深入Atlas系列:客户端网络访问基础结构(上) - WebRequest的工作流程与生命周期
- 深入Atlas 系列:客户端网络访问基础结构(上) - WebRequest的工作流程与生命周期
- 深入Atlas系列:客户端网络访问基础结构(上) - WebRequest的工作流程与生命周期
- 深入Atlas系列:Web Sevices Access in Atlas示例(5) - 自定义TypeConverter把基础类型转换为复杂类型
- 深入Atlas系列:Web Sevices Access in Atlas(7) - RTM中的客户端支持
- 深入Atlas系列:Web Sevices Access in Atlas(1) - 客户端支持
- 深入Atlas系列:Web Sevices Access in Atlas示例(5) - 自定义TypeConverter把基础类型转换为复杂类型
- 深入Atlas系列:客户端代码编写规则分析与指南
- 深入Atlas系列:综合示例(1) - 调用服务器端方法时直接获得客户端具体类型
- 深入Atlas系列:综合示例(1) - 调用服务器端方法时直接获得客户端具体类型
- 深入Atlas系列:Web Sevices Access in Atlas(1) - 客户端支持
- 深入Atlas系列:Web Sevices Access in Atlas(7) - RTM中的客户端支持
- 深入Atlas系列:Web Sevices Access in Atlas(7) - RTM中的客户端支持
- 深入Atlas系列:Web Sevices Access in Atlas示例(5) - 自定义TypeConverter把基础类型转换为复杂类型
- 深入Atlas系列:综合示例(1) - 调用服务器端方法时直接获得客户端具体类型