前面2篇文章我介绍了AJAXPro的一些实际应用.但是其基本的原理还不是很清楚,感觉做技术的,
最好还是"知其然知其所以然",这样才可以做到"有的放矢".
现在我们从头开始,了解AJAXPro的处理方式.
首先,我们在Web.config中添加了一段httpHandler注册.
<add path="*.ashx" verb="*" type="AjaxPro.AjaxHandlerFactory,AjaxPro.2"/>
这样,所有以ashx结尾的文件都由AjaxPro.AjaxHandlerFactory这个httpHandler处理.
打开使用AJAXPro注册的页面(就是使用AjaxPro.Utility.RegisterTypeForAjax(typeof(页面类))的页面).
查看其代码:发现他添加了4段js代码:
<script type="text/javascript" src="/ajaxpro/prototype.ashx"></script>
<script type="text/javascript" src="/ajaxpro/core.ashx"></script>
<script type="text/javascript" src="/ajaxpro/converter.ashx"></script>
<script type="text/javascript" src="/ajaxpro/SD2007.Test.Test1,SD2007.ashx"></script>
prototype.ashx就是prototype.js.一个著名的js开源AJAX框架.(地球人都知道吧!)
core.ashx是什么呢?
他其实就是一个js的类,这个类的作用是创建XMLHttpRequest对象进行异步操作.还有,XMLHttpRequest是依赖于浏览器的.
如果浏览器不能创建,则创建一个IFrameXmlHttp类来进行异步操作,也就是创建一个iframe.
converter.ashx是一个转换对象,用来把.Net数据类型转换到js中使用.前面一篇我的函数返回的是一个Hashtable 对象,但
他则转换成2维的数组.这个对象可以转换很多的.net数据类型.
SD2007.Test.Test1,SD2007.ashx就是我们页面注册AJAXPro对象产生的文件,他的代码不长,如下:
if
(
typeof
SD2007
==
"
undefined
"
) SD2007
=
{};
if
(
typeof
SD2007.Test
==
"
undefined
"
) SD2007.Test
=
{};
SD2007.Test.Test1_class
=
function
() {};
Object.extend(SD2007.Test.Test1_class.prototype, Object.extend(
new
AjaxPro.AjaxClass(), {
GetCity:
function
(sName) {
return
this
.invoke(
"
GetCity
"
, {
"
sName
"
:sName},
this
.GetCity.getArguments().slice(
1
));
},
url: '
/
ajaxpro
/
SD2007.Test.Test1,SD2007.ashx'
}));
SD2007.Test.Test1
=
new
SD2007.Test.Test1_class();
他利用prototype建立对象,并"继承"对象AjaxPro.AjaxClass,扩展一个GetCity方法,这个方法invoke调用ASP.Net里
注册为AJAXPro.Method的GetCity方法.也就是异步操作的核心.他其实也是利用其他的js框架来实现异步数据的操作,不
过"异步的调用一个方法"而已.我们现在看看其invoke方法的实现(红色的注释是我自己添加上去的):
invoke:
function
(method, args, callback, context) {
this
.__start
=
new
Date().getTime();
//
if(this.xmlHttp == null) {
this
.xmlHttp
=
new
XMLHttpRequest(); //建立异步对象
//
}
this
.isRunning
=
true
;
this
.method
=
method;
this
.args
=
args;
this
.callback
=
callback;
this
.context
=
context;
//是否使用异步操作
var
async
=
typeof
(callback)
==
"
function
"
&&
callback
!=
AjaxPro.noOperation;
if
(async) {
if
(MS.Browser.isIE) {
this
.xmlHttp.onreadystatechange
=
this
.doStateChange.bind(
this
);
}
else
{
this
.xmlHttp.onload
=
this
.doStateChange.bind(
this
);
this
.xmlHttp.onerror
=
this
.mozerror.bind(
this
);
}
this
.onLoading(
true
);
}
var
json
=
AjaxPro.toJSON(args)
+
""
; //转换成JSON数据
//下面做什么的不知道,好像是用了加密数据的
if
(AjaxPro.cryptProvider
!=
null
&&
typeof
AjaxPro.cryptProvider.encrypt
==
"
function
"
) {
json
=
AjaxPro.cryptProvider.encrypt(json);
}
this
.xmlHttp.open(
"
POST
"
,
this
.url, async); //post数据
this
.xmlHttp.setRequestHeader(
"
Content-Type
"
,
"
text/plain; charset=utf-8
"
);
this
.xmlHttp.setRequestHeader(
"
X-
"
+
AjaxPro.ID
+
"
-Method
"
, method);
if
(AjaxPro.token
!=
null
&&
AjaxPro.token.length
>
0
) {
this
.xmlHttp.setRequestHeader(
"
X-
"
+
AjaxPro.ID
+
"
-Token
"
, AjaxPro.token);
}
/*
if(!MS.Browser.isIE) {
this.xmlHttp.setRequestHeader("Connection", "close");
}
*/
//超时处理
this
.timeoutTimer
=
setTimeout(
this
.timeout.bind(
this
), AjaxPro.timeoutPeriod);
//发送数据
try
{
this
.xmlHttp.send(json); }
catch
(e){}
//
IE offline exception
if
(
!
async) {
return
this
.createResponse({error:
null
,value:
null
});
}
return
true
;
}
};
发现就是js调用XMLHttpRequest对象,然后发生数据进行异步操作而已.不过加了其他的一些他的代码.不过其中的
doStateChange函数比较的重要,他将会在服务端返回后执行.
doStateChange:
function
() {
this
.onStateChanged(
this
.xmlHttp.readyState,
this
);
if
(
this
.xmlHttp.readyState
!=
4
||
!
this
.isRunning) {
return
;
}
this
.duration
=
new
Date().getTime()
-
this
.__start;
if
(
this
.timeoutTimer
!=
null
) {
clearTimeout(
this
.timeoutTimer);
}
var
res
=
this
.getEmptyRes();
if
(
this
.xmlHttp.status
==
200
&&
this
.xmlHttp.statusText
==
"
OK
"
) {
res
=
this
.createResponse(res);
}
else
{
res
=
this
.createResponse(res,
true
);
res.error
=
{Message:
this
.xmlHttp.statusText,Type:
"
ConnectFailure
"
,Status:
this
.xmlHttp.status};
}
this
.endRequest(res);
},
如果 status 是 200,也就是 OK,那么清除掉超时处理函数,最后使用 callback 调用 createResponse 函数
还记得如果不是异步的话,createResponse 将会直接调用而不是通过 doStateChange 吧。
下面我们继续看看createResponse函数
createResponse:
function
(r, noContent) {
if
(
!
noContent) {
if
(
typeof
(
this
.xmlHttp.responseText)
==
"
unknown
"
) {
r.error
=
{Message:
"
XmlHttpRequest error reading property responseText.
"
, Type:
"
XmlHttpRequestException
"
};
return
r;
}
var
responseText
=
""
+
this
.xmlHttp.responseText;
//解密数据
if
(AjaxPro.cryptProvider
!=
null
&&
typeof
AjaxPro.cryptProvider.decrypt
==
"
function
"
) {
responseText
=
AjaxPro.cryptProvider.decrypt(responseText);
}
if
(
this
.xmlHttp.getResponseHeader(
"
Content-Type
"
)
==
"
text/xml
"
) {
r.value
=
this
.xmlHttp.responseXML;
}
else
{
if
(responseText
!=
null
&&
responseText.trim().length
>
0
) {
r.json
=
responseText;
var
v
=
null
;
eval(
"
v =
"
+
responseText
+
"
;
"
);
if
(v
!=
null
) {
if
(
typeof
v.value
!=
"
undefined
"
) r.value
=
v.value;
else
if
(
typeof
v.error
!=
"
undefined
"
) r.error
=
v.error;
}
}
}
}
/*
if(this.xmlHttp.getResponseHeader("X-" + AjaxPro.ID + "-Cache") == "server") {
r.isCached = true;
}
*/
//返回响应的数据
return
r;
}
如果前面的 json 也就是 Request 是加过密的,这里就需要对 responseText 进行解密。完了之后得到 r.value,r 将会被返回并提供给 callback 函数。r 被传入它的 res 参数。整个 Ajax ClientScript 的流程就差不多是完成了。
AJAXPro大概原理的探究已经差不多了,看完了他的实现,我才发现js真的是一个很"强悍"的语言.学好了,在web开
发上将会如虎添翼.
下篇介绍:(四)AJAXPro之旅---AJAXPro类库的探索...
(特补上IFrameXmlHttp的分析(加了注释了):)
AjaxPro.IFrameXmlHttp.prototype
=
{
onreadystatechange:
null
, headers: [], method:
"
POST
"
, url:
null
, async:
true
, iframe:
null
,
status:
0
, readyState:
0
, responseText:
null
,
abort:
function
() {
},
readystatechanged:
function
() {
var
doc
=
this
.iframe.contentDocument
||
this
.iframe.document;
if
(doc
!=
null
&&
doc.readyState
==
"
complete
"
&&
doc.body
!=
null
&&
doc.body.res
!=
null
) {
this
.status
=
200
;
this
.statusText
=
"
OK
"
;
this
.readyState
=
4
;
this
.responseText
=
doc.body.res;
//
设置响应文本为IFrame的documnet的内容
this
.onreadystatechange();
return
;
}
setTimeout(
this
.readystatechanged.bind(
this
),
10
);
},
open:
function
(method, url, async) {
if
(async
==
false
) {
alert(
"
Synchronous call using IFrameXMLHttp is not supported.
"
);
return
;
}
if
(
this
.iframe
==
null
) {
var
iframeID
=
"
hans
"
;
if
(document.createElement
&&
document.documentElement
&&
(window.opera
||
navigator.userAgent.indexOf('MSIE
5.0
')
==
-
1
))
{
//
建立iframe
var
ifr
=
document.createElement('iframe');
ifr.setAttribute('id', iframeID);
ifr.style.visibility
=
'hidden';
//
隐藏iframe
ifr.style.position
=
'absolute';
ifr.style.width
=
ifr.style.height
=
ifr.borderWidth
=
'0px';
this
.iframe
=
document.getElementsByTagName('body')[
0
].appendChild(ifr);
//
添加到body
}
else
if
(document.body
&&
document.body.insertAdjacentHTML)
{
//
也是建立iframe,考虑到浏览器兼容性
document.body.insertAdjacentHTML('beforeEnd', '
<
iframe name
=
"
' + iframeID + '
"
id
=
"
' + iframeID + '
"
style
=
"
border:1px solid black;display:none
"
></
iframe
>
');
}
//
设置iframe变量为刚才建立的iframe DOM对象
if
(window.frames
&&
window.frames[iframeID]) {
this
.iframe
=
window.frames[iframeID];
}
this
.iframe.name
=
iframeID;
this
.iframe.document.open();
this
.iframe.document.write(
"
<
"
+
"
html><
"
+
"
body></
"
+
"
body></
"
+
"
html>
"
);
this
.iframe.document.close();
}
this
.method
=
method;
this
.url
=
url;
this
.async
=
async;
},
//
建立响应头
setRequestHeader:
function
(name, value) {
for
(
var
i
=
0
; i
<
this
.headers.length; i
++
) {
if
(
this
.headers[i].name
==
name) {
this
.headers[i].value
=
value;
return
;
}
}
this
.headers.push({
"
name
"
:name,
"
value
"
:value});
},
getResponseHeader:
function
(name, value) {
return
null
;
},
//
建立input ,如果数据有回车则建立textarea
addInput:
function
(doc, form, name, value) {
var
ele;
var
tag
=
"
input
"
;
if
(value.indexOf(
"
\n
"
)
>=
0
) {
tag
=
"
textarea
"
;
}
if
(doc.all) {
ele
=
doc.createElement(
"
<
"
+
tag
+
"
name=\
""
+ name +
"
\
"
/>
"
);
}
else
{
ele
=
doc.createElement(tag);
ele.setAttribute(
"
name
"
, name);
}
ele.setAttribute(
"
value
"
, value);
form.appendChild(ele);
ele
=
null
;
},
send:
function
(data) {
if
(
this
.iframe
==
null
) {
return
;
}
var
doc
=
this
.iframe.contentDocument
||
this
.iframe.document;
var
form
=
doc.createElement(
"
form
"
);
//
建立一个form.设置其参数
doc.body.appendChild(form);
form.setAttribute(
"
action
"
,
this
.url);
form.setAttribute(
"
method
"
,
this
.method);
form.setAttribute(
"
enctype
"
,
"
application/x-www-form-urlencoded
"
);
for
(
var
i
=
0
; i
<
this
.headers.length; i
++
) {
switch
(
this
.headers[i].name.toLowerCase()) {
case
"
content-length
"
:
case
"
accept-encoding
"
:
case
"
content-type
"
:
break
;
default
:
this
.addInput(doc, form,
this
.headers[i].name,
this
.headers[i].value);
}
}
//
建立input ,如果数据有回车则建立textarea
this
.addInput(doc, form,
"
data
"
, data);
form.submit();
//
提交数据
setTimeout(
this
.readystatechanged.bind(
this
),
0
);
}
};