Ajax框架原理分析之Ext.Net
Ext.Net也是一個很有名的前端框架,它構(gòu)建于ExtJS之上,提供了一整套UI+AJAX的解決方案.通過對其源碼的研究,了解其是如何實現(xiàn)這套AJAX的,對我們也是很有幫助的.
1.靜態(tài)的AJAX方法實現(xiàn).
當把[DirectMethod]標記標在一個靜態(tài)方法上時,Ext.Net是通過HttpModule來截獲Http請求實現(xiàn)的.具體的實現(xiàn)類為:DirectRequestModule類
在應用程序生命周期的PostAcquireRequestState事件內(nèi)中加入處理函數(shù)
{
app.PostAcquireRequestState += OnPostAcquireRequestState;
app.PreSendRequestHeaders += RedirectPreSendRequestHeaders;
}
如果是AJAX請求且是靜態(tài)AJAX方法則調(diào)用ProcessRequest方法
代碼{
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);
}
}
}
通過反射來調(diào)用方法,并結(jié)束服務器處理過程,將結(jié)果返回到客戶端(有刪節(jié))
代碼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();
因為是直接通過反射來實現(xiàn),沒有執(zhí)行頁面的生命周期,所以Ext.Net官方推薦此種編寫方式.
2.實例的AJAX方法實現(xiàn).
我們要重點關注ResourceManager類,它是整個AJAX請求的核心
首先我們來看是如何實現(xiàn)AJAX方法的調(diào)動.
當客戶端發(fā)起一個AJAX請求時,Ext.Net會在POST數(shù)據(jù)中加入以下兩個鍵值對:
__EVENTARGUMENT:btnOK|event|Click
__EVENTVALIDATION:ResourceManager1
表示由ResourceManager類型的ResourceManager1實例來處理這次AJAX請求.具體發(fā)起請求的按鈕是btnOK,形式為事件,方式為Click.
在ResourceManager類的OnLoad事件中,如果是AJAX請求,則在LoadComplete事件中加入Page_AjaxLoadComplete處理函數(shù)
代碼{
base.OnLoad(e);
if (RequestManager.IsAjaxRequest && !this.Page.IsPostBack && !this.IsDynamic)
{
this.Page.LoadComplete += Page_AjaxLoadComplete;
}
}
在Page_AjaxLoadComplete處理函數(shù)中,借用回發(fā)的處理方法來實現(xiàn)AJAX調(diào)用:
代碼if (_ea.IsNotEmpty())
{
string _et = this.Page.Request["__EVENTTARGET"];
if (_et == this.UniqueID)
{
this.RaisePostBackEvent(_ea);
}
return;
}
在RaisePostBackEvent方法中,實現(xiàn)對具體控件具體方法的具體調(diào)用:
代碼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是如何將處理結(jié)果返回給客戶端的.
(29日繼續(xù))
首先我們要明白一點,后臺的Ext.Net控件在輸出到客戶端的并不是HTML代碼,而是JSON包裝后的JS代碼。瀏覽器執(zhí)行接收到的JS代碼后再生成HTML代碼。
我們知道,控件的輸出一般都寫在生命周期的Render事件中。XControl是所有Ext.Net控件的基類。這個類比較大,作者用了近十個文件,采用分部類的開發(fā)方式來實現(xiàn)這個類。Lifecycle.cs文件主要負責重寫控件的生命周期。在其重寫的Render中,調(diào)用了HtmlRender方法。此方法是一般的具體控件的實際輸出方法。另外在ResourceManager類的RenderAction方法中輸出頁面腳本注冊,樣式注冊,頁面初始化腳本等等。這些輸出不是單純的輸出HTML或JS代碼,而是在輸出的內(nèi)容的兩端加上了類似于“<Ext.Net.Direct.Response>”的標簽,這是為下一步輸出過濾做準備。
接著就要看輸出過濾了。我們知道,傳統(tǒng)的Asp.Net提交,服務器會完成整個頁面的生命周期,之后將處理過后的整個頁面的內(nèi)容返回。但是Ext.Net的AJAX提交走完了整個頁面的生命周期,返回的卻是Json數(shù)據(jù)。這其中倒底有什么玄機?答案就在輸出過濾!(光尋找這一點我就花了近三個小時,原因是我在聽WebCast的的時候,老趙說Asp.net Ajax框架是在Render事件上做手腳,對內(nèi)容進行了重輸出,我想Ext.Net應該差不多,于是使勁找,結(jié)果什么也找不到。 —_—!)
還是在DirectRequestModule類中:
{
app.ReleaseRequestState += AjaxRequestFilter;
}
一開始我以為沒什么用,現(xiàn)在才知道這是處理AJAX返回數(shù)據(jù)的關鍵!
代碼if (RequestManager.IsAjaxRequest)
{
if (response.ContentType.IsNotEmpty() && response.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
{
response.Filter = new AjaxRequestFilter(response.Filter);
}
}
在這里,AjaxRequestFilter類是主要實現(xiàn)類。其Flush方法是關鍵方法。 里面用到的DirectResponse類是返回的包裝類,將可能的返回信息封裝成了一個類,我們一步一步的看。
StringBuilder buffer = new StringBuilder(256);
DirectResponse ajaxResponse = new DirectResponse(true);
HttpContext context = HttpContext.Current;
這里是一些初始化的工作。其中html就是待過濾的原始的html代碼。
if (isUpdate != null && (bool)isUpdate)
{
this.ExtractUpdates(raw, ref buffer);
}
這里是從原始html提取更新的部份并寫到buffer中去。思路是用正則去匹配的,具體實現(xiàn)自行看代碼!
代碼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提取動態(tài)生成的html代碼,這里有個小插曲,如果isManual與raw滿意要求的話,就直接將提取的結(jié)果返回了。否則也將結(jié)果寫到buffer中。
代碼if (!ResourceManager.AjaxSuccess || error.IsNotEmpty())
{
ajaxResponse.Success = false;
if (error.IsNotEmpty())
{
ajaxResponse.ErrorMessage = error;
}
else
{
ajaxResponse.ErrorMessage = ResourceManager.AjaxErrorMessage;
}
}
這里是錯誤處理,就不多說了。
代碼{
ajaxResponse.ViewState = AjaxRequestFilter.GetHiddenInputValue(raw, BaseFilter.VIEWSTATE);
ajaxResponse.ViewStateEncrypted = AjaxRequestFilter.GetHiddenInputValue(raw, BaseFilter.VIEWSTATEENCRYPTED);
ajaxResponse.EventValidation = AjaxRequestFilter.GetHiddenInputValue(raw, BaseFilter.EVENTVALIDATION);
}
這里是從原始的html提取ViewState
代碼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類中獲取指定數(shù)據(jù)。
if (buffer.Length > 0)
{
ajaxResponse.Script = "<string>".ConcatWith(buffer.ToString());
}
這里是從原始的html提取被"<Ext.Net.Direct.Response>"標記的內(nèi)容。寫到buffer中,并終賦到ajaxResponse.Script屬性中。
代碼this.response.Write(data, 0, data.Length);
this.response.Flush();
最后,將結(jié)果通過DirectResponse類重寫過的ToString方法將結(jié)果序列化輸出到客戶端。
總結(jié)輸出的過程,其實是從原始的HTML代碼提取出相關信息填充到DirectResponse類的相關屬性中,再將其序列化到客戶端的過程。從ResourceManager獲取ServiceResponse,ExtraParamsResponse,Result,從原始HTML獲取ViewState,ViewStateEncrypted,EventValidation,從原始HTML獲取更新的數(shù)據(jù),動態(tài)的HTML內(nèi)容,"<Ext.Net.Direct.Response>"并填入Script屬性,跟據(jù)需要設置Success與ErrorMessage屬性。通過重寫DirectResponse類的ToString方法,來實現(xiàn)序列化過程。
以上就是我的分析。可能非常的粗枝大葉,但整個流程的基本架構(gòu)是分析出來了。 雖然代碼具體實現(xiàn)的好壞可能仁者見仁,智者見智,但里面真的還是有很多東西值得我去學習的!


浙公網(wǎng)安備 33010602011771號