Ext.Net也是一个很有名的前端框架,它构建于ExtJS之上,提供了一整套UI+AJAX的解决方案.通过对其源码的研究,了解其是如何实现这套AJAX的,对我们也是很有帮助的.
1.静态的AJAX方法实现.
当把[DirectMethod]标记标在一个静态方法上时,Ext.Net是通过HttpModule来截获Http请求实现的.具体的实现类为:DirectRequestModule类
在应用程序生命周期的PostAcquireRequestState事件内中加入处理函数
public
void
Init(HttpApplication app)
{
app.PostAcquireRequestState
+=
OnPostAcquireRequestState;
app.PreSendRequestHeaders
+=
RedirectPreSendRequestHeaders;
}
如果是AJAX请求且是静态AJAX方法则调用ProcessRequest方法
代码
private
void
OnPostAcquireRequestState(
object
sender, EventArgs eventArgs)
{
HttpApplication app
=
(HttpApplication)sender;
HttpRequest request
=
app.Context.Request;
if
(RequestManager.IsAjaxRequest)
{
if
(DirectMethod.IsStaticMethodRequest(request)
/*
|| Utilities.ReflectionUtils.IsTypeOf(app.Context.Handler, "System.Web.Script.Services.ScriptHandlerFactory+HandlerWrapper")
*/
)
{
this
.ProcessRequest(app, request);
}
}
}
通过反射来调用方法,并结束服务器处理过程,将结果返回到客户端(有删节)
代码
//
Get handler
HandlerMethods handler
=
HandlerMethods.GetHandlerMethods(context, request.FilePath);
//
Get method name to invoke
string
methodName
=
HandlerMethods.GetMethodName(context);
DirectMethod directMethod
=
handler.GetStaticMethod(methodName);
object
result
=
directMethod.Invoke();
app.Context.Response.Clear();
app.Context.Response.ClearContent();
app.Context.Response.ClearHeaders();
app.Context.Response.StatusCode
=
200
;
app.Context.Response.ContentType
=
"
application/json
"
;
app.Context.Response.Charset
=
"
utf-8
"
;
app.Context.Response.Cache.SetNoServerCaching();
app.Context.Response.Cache.SetMaxAge(TimeSpan.Zero);
app.Context.Response.Write(responseObject.ToString());
app.CompleteRequest();
因为是直接通过反射来实现,没有执行页面的生命周期,所以Ext.Net官方推荐此种编写方式.
2.实例的AJAX方法实现.
我们要重点关注ResourceManager类,它是整个AJAX请求的核心
首先我们来看是如何实现AJAX方法的调动.
当客户端发起一个AJAX请求时,Ext.Net会在POST数据中加入以下两个键值对:
__EVENTARGUMENT:btnOK|event|Click
__EVENTVALIDATION:ResourceManager1
表示由ResourceManager类型的ResourceManager1实例来处理这次AJAX请求.具体发起请求的按钮是btnOK,形式为事件,方式为Click.
在ResourceManager类的OnLoad事件中,如果是AJAX请求,则在LoadComplete事件中加入Page_AjaxLoadComplete处理函数
代码
protected
override
void
OnLoad(EventArgs e)
{
base
.OnLoad(e);
if
(RequestManager.IsAjaxRequest
&&
!
this
.Page.IsPostBack
&&
!
this
.IsDynamic)
{
this
.Page.LoadComplete
+=
Page_AjaxLoadComplete;
}
}
在Page_AjaxLoadComplete处理函数中,借用回发的处理方法来实现AJAX调用:
代码
string
_ea
=
this
.Page.Request[
"
__EVENTARGUMENT
"
];
if
(_ea.IsNotEmpty())
{
string
_et
=
this
.Page.Request[
"
__EVENTTARGET
"
];
if
(_et
==
this
.UniqueID)
{
this
.RaisePostBackEvent(_ea);
}
return
;
}
在RaisePostBackEvent方法中,实现对具体控件具体方法的具体调用:
代码
string
controlID
=
args[
0
];
string
controlEvent
=
args[
2
];
ctrl
=
ControlUtils.FindControlByClientID(
this
.Page, controlID,
true
,
null
);
case
AjaxRequestType.Event:
Observable observable
=
ctrl
as
Observable;
if
(observable
==
null
)
{
if
(ctrl
is
ResourceManagerProxy)
{
((ResourceManagerProxy)ctrl).FireAsyncEvent(controlEvent, extraParams);
}
else
if
(ctrl
is
ResourceManager)
{
this
.FireAsyncEvent(controlEvent, extraParams);
}
else
{
throw
new
HttpException(
"
The control with ID '{0}' is not Observable
"
.FormatWith(controlID));
}
}
if
(observable
!=
null
)
{
observable.FireAsyncEvent(controlEvent, extraParams);
}
break
;
然后我们来看Ext.Net是如何将处理结果返回给客户端的.
(29日继续)
首先我们要明白一点,后台的Ext.Net控件在输出到客户端的并不是HTML代码,而是JSON包装后的JS代码。浏览器执行接收到的JS代码后再生成HTML代码。
我们知道,控件的输出一般都写在生命周期的Render事件中。XControl是所有Ext.Net控件的基类。这个类比较大,作者用了近十个文件,采用分部类的开发方式来实现这个类。Lifecycle.cs文件主要负责重写控件的生命周期。在其重写的Render中,调用了HtmlRender方法。此方法是一般的具体控件的实际输出方法。另外在ResourceManager类的RenderAction方法中输出页面脚本注册,样式注册,页面初始化脚本等等。这些输出不是单纯的输出HTML或JS代码,而是在输出的内容的两端加上了类似于“<Ext.Net.Direct.Response>”的标签,这是为下一步输出过滤做准备。
接着就要看输出过滤了。我们知道,传统的Asp.Net提交,服务器会完成整个页面的生命周期,之后将处理过后的整个页面的内容返回。但是Ext.Net的AJAX提交走完了整个页面的生命周期,返回的却是Json数据。这其中倒底有什么玄机?答案就在输出过滤!(光寻找这一点我就花了近三个小时,原因是我在听WebCast的的时候,老赵说Asp.net Ajax框架是在Render事件上做手脚,对内容进行了重输出,我想Ext.Net应该差不多,于是使劲找,结果什么也找不到。 —_—!)
还是在DirectRequestModule类中:
public
void
Init(HttpApplication app)
{
app.ReleaseRequestState
+=
AjaxRequestFilter;
}
一开始我以为没什么用,现在才知道这是处理AJAX返回数据的关键!
代码
HttpResponse response
=
HttpContext.Current.Response;
if
(RequestManager.IsAjaxRequest)
{
if
(response.ContentType.IsNotEmpty()
&&
response.ContentType.Equals(
"
text/html
"
, StringComparison.InvariantCultureIgnoreCase))
{
response.Filter
=
new
AjaxRequestFilter(response.Filter);
}
}
在这里,AjaxRequestFilter类是主要实现类。其Flush方法是关键方法。 里面用到的DirectResponse类是返回的包装类,将可能的返回信息封装成了一个类,我们一步一步的看。
string
raw
=
this
.html.ToString();
StringBuilder buffer
=
new
StringBuilder(
256
);
DirectResponse ajaxResponse
=
new
DirectResponse(
true
);
HttpContext context
=
HttpContext.Current;
这里是一些初始化的工作。其中html就是待过滤的原始的html代码。
object
isUpdate
=
context.Items[
"
Ext.Net.Direct.Update
"
];
if
(isUpdate
!=
null
&&
(
bool
)isUpdate)
{
this
.ExtractUpdates(raw,
ref
buffer);
}
这里是从原始html提取更新的部份并写到buffer中去。思路是用正则去匹配的,具体实现自行看代码!
代码
string
dynamicHtml
=
this
.ExtractDynamicHtml(raw);
object
isManual
=
context.Items[
"
Ext.Net.Direct.Response.Manual
"
];
if
(isManual
!=
null
&&
(
bool
)isManual)
{
if
(raw.StartsWith(
"
<Ext.Net.Direct.Response.Manual>
"
))
{
string
script
=
dynamicHtml.ConcatWith(raw.RightOf(
"
<Ext.Net.Direct.Response.Manual>
"
).LeftOf(
"
</Ext.Net.Direct.Response.Manual>
"
));
byte
[] rsp
=
System.Text.Encoding.UTF8.GetBytes(script);
this
.response.Write(rsp,
0
, rsp.Length);
this
.response.Flush();
return
;
}
}
buffer.Append(dynamicHtml);
这里是从原始的html提取动态生成的html代码,这里有个小插曲,如果isManual与raw满意要求的话,就直接将提取的结果返回了。否则也将结果写到buffer中。
代码
string
error
=
context
==
null
?
null
: (context.Error
!=
null
?
context.Error.ToString() :
null
);
if
(
!
ResourceManager.AjaxSuccess
||
error.IsNotEmpty())
{
ajaxResponse.Success
=
false
;
if
(error.IsNotEmpty())
{
ajaxResponse.ErrorMessage
=
error;
}
else
{
ajaxResponse.ErrorMessage
=
ResourceManager.AjaxErrorMessage;
}
}
这里是错误处理,就不多说了。
代码
if
(ResourceManager.ReturnViewState)
{
ajaxResponse.ViewState
=
AjaxRequestFilter.GetHiddenInputValue(raw, BaseFilter.VIEWSTATE);
ajaxResponse.ViewStateEncrypted
=
AjaxRequestFilter.GetHiddenInputValue(raw, BaseFilter.VIEWSTATEENCRYPTED);
ajaxResponse.EventValidation
=
AjaxRequestFilter.GetHiddenInputValue(raw, BaseFilter.EVENTVALIDATION);
}
这里是从原始的html提取ViewState
代码
object
obj
=
ResourceManager.ServiceResponse;
if
(obj
is
Response)
{
ajaxResponse.ServiceResponse
=
new
ClientConfig().Serialize(obj);
}
else
{
ajaxResponse.ServiceResponse
=
obj
!=
null
?
JSON.Serialize(obj) :
null
;
}
if
(ResourceManager.ExtraParamsResponse.Count
>
0
)
{
ajaxResponse.ExtraParamsResponse
=
ResourceManager.ExtraParamsResponse.ToJson();
}
if
(ResourceManager.DirectMethodResult
!=
null
)
{
ajaxResponse.Result
=
ResourceManager.DirectMethodResult;
}
这里是从ResourceManager类中获取指定数据。
buffer.Append(raw.RightOf(
"
<Ext.Net.Direct.Response>
"
).LeftOf(
"
</Ext.Net.Direct.Response>
"
));
if
(buffer.Length
>
0
)
{
ajaxResponse.Script
=
"
<string>
"
.ConcatWith(buffer.ToString());
}
这里是从原始的html提取被"<Ext.Net.Direct.Response>"标记的内容。写到buffer中,并终赋到ajaxResponse.Script属性中。
代码
byte
[] data
=
System.Text.Encoding.UTF8.GetBytes((isUpload
?
"
<textarea>
"
:
""
)
+
ajaxResponse.ToString()
+
(isUpload
?
"
</textarea>
"
:
""
) );
this
.response.Write(data,
0
, data.Length);
this
.response.Flush();
最后,将结果通过DirectResponse类重写过的ToString方法将结果序列化输出到客户端。
总结输出的过程,其实是从原始的HTML代码提取出相关信息填充到DirectResponse类的相关属性中,再将其序列化到客户端的过程。从ResourceManager获取ServiceResponse,ExtraParamsResponse,Result,从原始HTML获取ViewState,ViewStateEncrypted,EventValidation,从原始HTML获取更新的数据,动态的HTML内容,"<Ext.Net.Direct.Response>"并填入Script属性,跟据需要设置Success与ErrorMessage属性。通过重写DirectResponse类的ToString方法,来实现序列化过程。
以上就是我的分析。可能非常的粗枝大叶,但整个流程的基本架构是分析出来了。 虽然代码具体实现的好坏可能仁者见仁,智者见智,但里面真的还是有很多东西值得我去学习的!