編寫輕量ajax組件02--淺析AjaxPro的示例分析,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎn)單易行的方法。
創(chuàng)新互聯(lián)成立以來不斷整合自身及行業(yè)資源、不斷突破觀念以使企業(yè)策略得到完善和成熟,建立了一套“以技術(shù)為基點(diǎn),以客戶需求中心、市場(chǎng)為導(dǎo)向”的快速反應(yīng)體系。對(duì)公司的主營(yíng)項(xiàng)目,如中高端企業(yè)網(wǎng)站企劃 / 設(shè)計(jì)、行業(yè) / 企業(yè)門戶設(shè)計(jì)推廣、行業(yè)門戶平臺(tái)運(yùn)營(yíng)、App定制開發(fā)、手機(jī)網(wǎng)站制作設(shè)計(jì)、微信網(wǎng)站制作、軟件開發(fā)、西云機(jī)房等實(shí)行標(biāo)準(zhǔn)化操作,讓客戶可以直觀的預(yù)知到從創(chuàng)新互聯(lián)可以獲得的服務(wù)效果。一、ajaxpro的使用
我們先來看這個(gè)組件如何使用。
1. 注冊(cè)AjaxHandlerFactory
在web.config里進(jìn)行如下配置:
簡(jiǎn)單的說,請(qǐng)求的url符合 ajaxpro/*.ashx 格式的,都會(huì)被AjaxHandlerFactory處理,這是一個(gè)實(shí)現(xiàn)IHandlerFactory接口的工廠類,用來獲取IHandler處理程序。其中type的格式是:"名稱控件.類名稱,程序集名稱"。
2. 在頁面類Page_Load事件進(jìn)行注冊(cè)
protected void Page_Load(object sender, EventArgs e) { AjaxPro.Utility.RegisterTypeForAjax(typeof(AjaxProPage)); }
我們傳遞了本頁面對(duì)象的Type給ResisterTypoForAjax方法,這個(gè)方法用來在前臺(tái)注冊(cè)腳本,具體會(huì)調(diào)用當(dāng)前Page對(duì)象的RegisterClientScriptBlock進(jìn)行注冊(cè),所以.aspx文件中必須有一個(gè)
,否則腳本將無法注冊(cè)。(這里傳遞了Type,實(shí)際也可以做到不用傳遞的,內(nèi)部通過HttpContext.Current.Handler.GetType().BaseType 也可以獲得這個(gè)類型)3.用AjaxMethod標(biāo)記方法
[AjaxMethod] public ListGetList(string input1,string input2) { return new List { input1, input2 }; }
AjaxMethod是一個(gè)標(biāo)記屬性,表示這個(gè)方法用于處理ajax請(qǐng)求,它最終通過反射執(zhí)行;它有幾個(gè)構(gòu)造函數(shù)對(duì),對(duì)于有些需要緩存的數(shù)據(jù),可以設(shè)置緩存時(shí)間;如果我們的請(qǐng)求不需要使用Session,可以設(shè)置HttpSessionStateRequirement;如果請(qǐng)求需要異步,例如請(qǐng)求一個(gè)耗時(shí)的web服務(wù),也可以設(shè)置處理程序?yàn)楫惒綘顟B(tài)。
方法的返回值可以是簡(jiǎn)單的類型,也可以是復(fù)雜的類型;例如集合類型在前臺(tái)獲得就是一個(gè)數(shù)組。
4.前臺(tái)調(diào)用
后臺(tái)的配置和使用都非常簡(jiǎn)單,接下來我們看前臺(tái)如何發(fā)起請(qǐng)求。
function GetList() { //var result = AjaxProNamespace.AjaxProPage.GetList("a", "b").value; //console.log(result); AjaxProNamespace.AjaxProPage.GetList("a", "b", function (result) { console.log(result); }); }
這里AjaxProNamespace 是頁面類所在的名稱空間,AjaxProPage 就是頁面類的名稱,GetList是標(biāo)記的方法。為什么可以這樣寫呢?前面說到,ajaxpro會(huì)在前臺(tái)注冊(cè)腳本,它會(huì)根據(jù)我們頁面對(duì)象的相關(guān)信息生成如下腳本,所以我們才可以這樣調(diào)用,而完全不用自己寫js或者用jquery庫(kù)的方法。
if(typeof AjaxProNamespace == "undefined") AjaxProNamespace={}; if(typeof AjaxProNamespace.AjaxProPage_class == "undefined") AjaxProNamespace.AjaxProPage_class={}; AjaxProNamespace.AjaxProPage_class = function() {}; Object.extend(AjaxProNamespace.AjaxProPage_class.prototype, Object.extend(new AjaxPro.AjaxClass(), { GetList: function(input1, input2) { return this.invoke("GetList", {"input1":input1, "input2":input2}, this.GetList.getArguments().slice(2)); }, url: '/ajaxpro/AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode.ashx' })); AjaxProNamespace.AjaxProPage = new AjaxProNamespace.AjaxProPage_class();
GetList的參數(shù)對(duì)應(yīng)后臺(tái)方法的參數(shù),類型必須可以轉(zhuǎn)換,否則調(diào)用會(huì)失敗。最后一個(gè)參數(shù)為回調(diào)函數(shù),回調(diào)函數(shù)的參數(shù)是對(duì)返回結(jié)果進(jìn)行封裝的對(duì)象,其value屬性就是執(zhí)行成功返回的值,如上面返回的就是一個(gè)數(shù)組對(duì)象。其error包括了失敗的信息。
注意,上面注釋掉的部分是同步請(qǐng)求的做法,這往往不是我們想要的,我曾經(jīng)就見過有人這樣錯(cuò)誤的使用。
二、ajaxpro處理請(qǐng)求原理
這里主要關(guān)注組件處理ajax請(qǐng)求的過程,其它輔助功能不做介紹。
1.生成輔助腳本
在Page_Load事件里我們調(diào)用了AjaxPro.Utility.RegisterTypeForAjax(typeof(AjaxProPage)); 用來注冊(cè)所需要的腳本。我們注意到在前臺(tái)頁面引入了如下腳本:
也就是每個(gè)頁面都會(huì)都會(huì)發(fā)起這幾個(gè)請(qǐng)求。這幾個(gè)都是.ashx結(jié)尾的文件,但實(shí)際里面都是js代碼;這些js有的是作為資源嵌套在dll內(nèi)部,有的是自動(dòng)生成的,主要是封裝了ajax請(qǐng)求相關(guān)方法,以及讓我們可以用:名稱空間.頁面類名稱.標(biāo)記方法名稱 這樣去調(diào)用方法。為什么要用.ashx而不是用.js呢?因?yàn)樽鳛榻M件內(nèi)部的資源文件,外部無法直接請(qǐng)求.js文件,而.ashx可以被攔截,然后用Response.Write將內(nèi)容輸出。
如果每次都生成和發(fā)送這些腳本的效率是很低的,ajaxpro內(nèi)部的處理是判斷請(qǐng)求頭的If-None-Math和If-Modified-Since,如果兩個(gè)都和緩存的一樣,就返回一個(gè)304狀態(tài)碼。所以,客戶端只有首次請(qǐng)求服務(wù)端會(huì)返回文件的內(nèi)容,后續(xù)的都只返回304表示使用本地緩存。我們刷新頁面可以驗(yàn)證這個(gè)過程:
2. 攔截請(qǐng)求
HttpHandler(IHttpHandler) 和 HttpModule(IHttpModule) 是asp.net 兩個(gè)重要的組件,讓我們可以在asp.net的基礎(chǔ)上很方便的進(jìn)行擴(kuò)展。HttpHandler對(duì)應(yīng)某種具體的請(qǐng)求,例如.ashx,.aspx等;HttpModule是一個(gè)攔截器,可以在管道的某個(gè)事件對(duì)所有請(qǐng)求進(jìn)行攔截。簡(jiǎn)單的說,在管道中,HttpApplication會(huì)觸發(fā)一系列事件,我們?cè)谕ㄟ^HttpModule對(duì)某個(gè)事件進(jìn)行注冊(cè),例如我們可以在處理程序?qū)ο笊汕皵r截請(qǐng)求,然后映射到自己的處理程序;而實(shí)際處理請(qǐng)求返回結(jié)果的是HttpHandler,例如Page用來生成html。
以asp.net mvc框架為例,它是建立在asp.net 路由機(jī)制的基礎(chǔ)上的,asp.net 路由系統(tǒng)通過一個(gè)UrlRoutingModule對(duì)請(qǐng)求進(jìn)行攔截,具體是在PostResolveRequestCache事件進(jìn)行攔截,對(duì)url進(jìn)行解析,封裝相應(yīng)的路由數(shù)據(jù)后,最終將請(qǐng)求交給一個(gè)MvcHandler進(jìn)行處理,MvcHandler實(shí)現(xiàn)了IHttpHandler接口。
前面我們進(jìn)行了如下配置:
IHttpHandler的ProcessRequest(異步就是BeginProcessRequest)就用來執(zhí)行請(qǐng)求返回輸出結(jié)果的。如果只需要一種處理程序我們也可以實(shí)現(xiàn)IHttpHandler。IHandlerFactory的定義如下:
public interface IHttpHandlerFactory { IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated); void ReleaseHandler(IHttpHandler handler); }
所以,ajaxpro的所有請(qǐng)求都會(huì)符合ajaxpro/*.ashx格式,然后在GetHandler方法,就可以進(jìn)行具體的處理,返回結(jié)果是IHttpHandler;以非異步狀態(tài)為例,如果我們配置了需要Session,就會(huì)生成一個(gè)實(shí)現(xiàn)IHttpHandler和IRequiresSessionState的Handler,如果需要只讀的Session,就會(huì)生成一個(gè)實(shí)現(xiàn)IHttpHandler和IReadOnlySessionState的Handler;這些信息可以通過反射從AjaxMethod標(biāo)記屬性獲得。AjaxHandlerFactory的主要代碼如下:
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated) { string filename = Path.GetFileNameWithoutExtension(context.Request.Path); Type t = null; Exception typeException = null; bool isInTypesList = false; switch (requestType) { //Get請(qǐng)求,獲取前面的那4個(gè)腳本 case "GET": switch (filename.ToLower()) { case "prototype": return new EmbeddedJavaScriptHandler("prototype"); case "core": return new EmbeddedJavaScriptHandler("core"); case "ms": return new EmbeddedJavaScriptHandler("ms"); case "prototype-core": case "core-prototype": return new EmbeddedJavaScriptHandler("prototype,core"); case "converter": return new ConverterJavaScriptHandler(); default: return new TypeJavaScriptHandler(t); } case "POST": IAjaxProcessor[] p = new IAjaxProcessor[2]; p[0] = new XmlHttpRequestProcessor(context, t); p[1] = new IFrameProcessor(context, t); for (int i = 0; i < p.Length; i++) { if (p[i].CanHandleRequest) { //獲取標(biāo)記方法的AjaxMethod屬性 AjaxMethodAttribute[] ma = (AjaxMethodAttribute[])p[i].AjaxMethod.GetCustomAttributes(typeof(AjaxMethodAttribute), true); bool useAsync = false; HttpSessionStateRequirement sessionReq = HttpSessionStateRequirement.ReadWrite; if (ma.Length > 0) { useAsync = ma[0].UseAsyncProcessing; if (ma[0].RequireSessionState != HttpSessionStateRequirement.UseDefault) sessionReq = ma[0].RequireSessionState; } //6種Handler,根據(jù)是否異步,session狀態(tài)返回指定的Handler switch (sessionReq) { case HttpSessionStateRequirement.Read: if (!useAsync) return new AjaxSyncHttpHandlerSessionReadOnly(p[i]); else return new AjaxAsyncHttpHandlerSessionReadOnly(p[i]); case HttpSessionStateRequirement.ReadWrite: if (!useAsync) return new AjaxSyncHttpHandlerSession(p[i]); else return new AjaxAsyncHttpHandlerSession(p[i]); case HttpSessionStateRequirement.None: if (!useAsync) return new AjaxSyncHttpHandler(p[i]); else return new AjaxAsyncHttpHandler(p[i]); default: if (!useAsync) return new AjaxSyncHttpHandlerSession(p[i]); else return new AjaxAsyncHttpHandlerSession(p[i]); } } } break; } return null; }
3. 反射執(zhí)行方法
當(dāng)獲得一個(gè)處理本次請(qǐng)求的Handler后,就可以在其ProcessRequest(異步為BeginProcessRequest)執(zhí)行指定的方法。要執(zhí)行一個(gè)頁面對(duì)象的方法,我們必須知道指定頁面所在的程序集,名稱空間,頁面類的名稱以及方法的名稱。這似乎符合我們前面:名稱空間.類名稱.方法名稱的調(diào)用方式。為了與一般請(qǐng)求區(qū)分開,讓組件具有足夠的獨(dú)立性,ajaxpro只攔截符合"ajaxpro/*.ashx格式的請(qǐng)求,這說明我們的ajax請(qǐng)求也要符合這個(gè)格式。如:http://localhost:50712/ajaxpro/AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode.ashx,這個(gè)格式由前臺(tái)腳本自動(dòng)生成,并不需要我們?nèi)?gòu)造。仔細(xì)觀察,會(huì)發(fā)現(xiàn)AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode 就是頁面類的完全限定名:名稱空間.類名稱,程序集名稱,通過這個(gè)我們就可以生成具體的Type,然后進(jìn)行反射獲取信息。那么方法的名稱呢?ajaxpro將其放在http header 中,名稱為:X-AjaxPro-Method。有了這些信息,就可以反射執(zhí)行方法了。這里核心代碼為:
internal void Run() { try { //設(shè)置輸出結(jié)果不緩存(這不一定是我們想要的) p.Context.Response.Expires = 0; p.Context.Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache); p.Context.Response.ContentType = p.ContentType; p.Context.Response.ContentEncoding = System.Text.Encoding.UTF8; //驗(yàn)證ajax請(qǐng)求 if (!p.IsValidAjaxToken()) { p.SerializeObject(new System.Security.SecurityException("The AjaxPro-Token is not valid.")); return; } //方法參數(shù)對(duì)象數(shù)組 object[] po = null; //請(qǐng)求處理結(jié)果 object res = null; try { //獲取參數(shù) po = p.RetreiveParameters(); } catch (Exception ex){} //獲取緩存的Key string cacheKey = p.Type.FullName + "|" + p.GetType().Name + "|" + p.AjaxMethod.Name + "|" + p.GetHashCode(); if (p.Context.Cache[cacheKey] != null) { //如果緩存存在,則直接使用緩存 p.Context.Response.AddHeader("X-" + Constant.AjaxID + "-Cache", "server"); p.Context.Response.Write(p.Context.Cache[cacheKey]); return; } try { if (p.AjaxMethod.IsStatic) { //使用反射調(diào)用靜態(tài)方法 try { res = p.Type.InvokeMember( p.AjaxMethod.Name, System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.InvokeMethod, null, null, po); } catch (Exception ex){} } else { try { //創(chuàng)建實(shí)例對(duì)象,反射調(diào)用實(shí)例方法 object c = (object)Activator.CreateInstance(p.Type, new object[] { }); if (c != null) { res = p.AjaxMethod.Invoke(c, po); } } catch (Exception ex){} } } catch (Exception ex){} try { //判斷結(jié)果是不是xml,如是設(shè)置ContentType if (res != null && res.GetType() == typeof(System.Xml.XmlDocument)) { p.Context.Response.ContentType = "text/xml"; p.Context.Response.ContentEncoding = System.Text.Encoding.UTF8; ((System.Xml.XmlDocument)res).Save(p.Context.Response.OutputStream); return; } string result = null; ; System.Text.StringBuilder sb = new System.Text.StringBuilder(); try { result = p.SerializeObject(res); } catch (Exception ex){} //如果需要緩存,則將結(jié)果寫入緩存 if (p.ServerCacheAttributes.Length > 0) { if (p.ServerCacheAttributes[0].IsCacheEnabled) { p.Context.Cache.Add(cacheKey, result, null, DateTime.Now.Add(p.ServerCacheAttributes[0].CacheDuration), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null); } } } catch (Exception ex){} } catch (Exception ex){} }
三、總結(jié)
我們總結(jié)一下ajaxpro的核心處理流程,它通過一個(gè)IHttpHandlerFactory攔截指定格式的url,然后從中獲取類型的完全限定名生成類型對(duì)象,接著通過反射獲取標(biāo)記方法的特性,生成一個(gè)自定義的實(shí)現(xiàn)IHttpHandler接口的對(duì)象;在其ProcessRequest方法中,從http headers獲取方法名稱,通過反射進(jìn)行參數(shù)映射并執(zhí)行函數(shù)。
ajaxpro 具有如下優(yōu)點(diǎn):
1. 配置簡(jiǎn)單。
2. 可以配合其它組件一起使用。
3. 封裝前臺(tái)腳本,我們不用自己封裝或者使用其它腳本庫(kù)。
4. 對(duì)返回值處理,我們可以返回簡(jiǎn)單類型或者復(fù)雜類型都會(huì)自動(dòng)序列化。
缺點(diǎn)是:
1. 頁面會(huì)多出4個(gè)請(qǐng)求。盡管會(huì)利用304緩存,但還是需要發(fā)送請(qǐng)求到服務(wù)器。
2. ajax無法使用Get請(qǐng)求。由于自定義了url格式,使用這種格式就無法用Get請(qǐng)求了,我們知道Get請(qǐng)求是可以被瀏覽器緩存的,雅虎前端優(yōu)化建議中有一條就是多用get請(qǐng)求。事實(shí)上,應(yīng)該把名稱空間.類名稱,程序集放到http header中,然后提供了一個(gè)type類型的參數(shù)讓我們自由選擇。
3. 與