編寫輕量ajax組件02--淺析AjaxPro_AJAX相關

來源:互聯網
上載者:User

前言

  上一篇介紹了在webform平台實現ajax的一些方式,並且實現一個基類。這一篇我們來看一個開源的組件:ajaxpro。雖然這是一個比較老的組件,不過實現思想和源碼還是值得我們學習的。通過上一篇的介紹,我們知道要調用頁面對象的方法,就是靠反射來實現的,關鍵是整個處理過程,包括反射調用方法、參數映射等。ajaxpro不僅在後台幫我們實現了這個過程,在前台也封裝了請求調用的方法,例如ajax的相關方法,用ajaxpro的方法就可以發送非同步請求了,不需要自己封裝js或者使用js庫。接下來就對這個組件進行淺析。

一、ajaxpro的使用

  我們先來看這個組件如何使用。

  1. 註冊AjaxHandlerFactory

  在web.config裡進行如下配置:

<httpHandlers> <add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro"/></httpHandlers>

  簡單的說,請求的url符合 ajaxpro/*.ashx 格式的,都會被AjaxHandlerFactory處理,這是一個實現IHandlerFactory介面的工廠類,用來擷取IHandler處理常式。其中type的格式是:"名稱控制項.類名稱,程式集名稱"。

  2. 在頁面類Page_Load事件進行註冊

protected void Page_Load(object sender, EventArgs e){ AjaxPro.Utility.RegisterTypeForAjax(typeof(AjaxProPage));}

  我們傳遞了本頁面對象的Type給ResisterTypoForAjax方法,這個方法用來在前台註冊指令碼,具體會調用當前Page對象的RegisterClientScriptBlock進行註冊,所以.aspx檔案中必須有一個<form runat="server"></form>,否則指令碼將無法註冊。(這裡傳遞了Type,實際也可以做到不用傳遞的,內部通過HttpContext.Current.Handler.GetType().BaseType 也可以獲得這個類型)

  3.用AjaxMethod標記方法  

[AjaxMethod]public List<string> GetList(string input1,string input2){ return new List<string> { input1, input2 };}

  AjaxMethod是一個標記屬性,表示這個方法用於處理ajax請求,它最終通過反射執行;它有幾個建構函式對,對於有些需要緩衝的資料,可以設定緩衝時間;如果我們的請求不需要使用Session,可以設定HttpSessionStateRequirement;如果請求需要非同步,例如請求一個耗時的web服務,也可以設定處理常式為非同步狀態。

  方法的傳回值可以是簡單的類型,也可以是複雜的類型;例如集合類型在前台獲得就是一個數組。

  4.前台調用

  背景配置和使用都非常簡單,接下來我們看前台如何發起請求。

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是標記的方法。為什麼可以這樣寫呢?前面說到,ajaxpro會在前台註冊指令碼,它會根據我們頁面對象的相關資訊產生如下指令碼,所以我們才可以這樣調用,而完全不用自己寫js或者用jquery庫的方法。

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的參數對應後台方法的參數,類型必須可以轉換,否則調用會失敗。最後一個參數為回呼函數,回呼函數的參數是對返回結果進行封裝的對象,其value屬性就是執行成功返回的值,如上面返回的就是一個數組對象。其error包括了失敗的資訊。

  注意,上面注釋掉的部分是同步請求的做法,這往往不是我們想要的,我曾經就見過有人這樣錯誤的使用。

二、ajaxpro處理請求原理

  這裡主要關注組件處理ajax請求的過程,其它協助工具功能不做介紹。

  1.產生輔助指令碼

  在Page_Load事件裡我們調用了AjaxPro.Utility.RegisterTypeForAjax(typeof(AjaxProPage)); 用來註冊所需要的指令碼。我們注意到在前台頁面引入了如下指令碼:

也就是每個頁面都會都會發起這幾個請求。這幾個都是.ashx結尾的檔案,但實際裡面都是js代碼;這些js有的是作為資源嵌套在dll內部,有的是自動產生的,主要是封裝了ajax請求相關方法,以及讓我們可以用:名稱空間.頁面類名稱.標記方法名稱 這樣去調用方法。為什麼要用.ashx而不是用.js呢?因為作為組件內部的資源檔,外部無法直接請求.js檔案,而.ashx可以被攔截,然後用Response.Write將內容輸出。

  如果每次都產生和發送這些指令碼的效率是很低的,ajaxpro內部的處理是判斷要求標頭的If-None-Math和If-Modified-Since,如果兩個都和緩衝的一樣,就返回一個304狀態代碼。所以,用戶端只有首次請求服務端會返迴文件的內容,後續的都只返回304表示使用本機快取。我們重新整理頁面可以驗證這個過程:

  2. 攔截請求

  HttpHandler(IHttpHandler) 和 HttpModule(IHttpModule) 是asp.net 兩個重要的組件,讓我們可以在asp.net的基礎上很方便的進行擴充。HttpHandler對應某種具體的請求,例如.ashx,.aspx等;HttpModule是一個攔截器,可以在管道的某個事件對所有請求進行攔截。簡單的說,在管道中,HttpApplication會觸發一系列事件,我們在通過HttpModule對某個事件進行註冊,例如我們可以在處理常式對象產生前攔截請求,然後映射到自己的處理常式;而實際處理請求返回結果的是HttpHandler,例如Page用來產生html。

  以asp.net mvc架構為例,它是建立在asp.net 路由機制的基礎上的,asp.net 路由系統通過一個UrlRoutingModule對請求進行攔截,具體是在PostResolveRequestCache事件進行攔截,對url進行解析,封裝相應的路由資料後,最終將請求交給一個MvcHandler進行處理,MvcHandler實現了IHttpHandler介面。

  前面我們進行了如下配置:<add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro"/> 這表明了任何的以 ajaxpro/任意名稱.ashx結尾的 Post/Get 請求,都交給AjaxPro.AjaxHandlerFactory進行處理,它是一個實現了IHandlerFactory的處理常式工廠,用來產生具體的IHttpHandler。組件內部定義了多個實現IHttpHandler的類,有的是為了產生js指令碼的,對於處理ajax請求,主要分為兩類:非同步(IHttpAsyncHandler)和非非同步(IHttpHandler);在這兩類的基礎上,對於Session的狀態的支援又分為三種:支援讀寫(實現IRequiresSessionState標記介面)的Handler、唯讀(實現IReadOnlySessionState標記介面)的Handler和不支援Session的Handler。具體產生什麼樣的Handler是通過AjaxMethod進行判斷的。

  IHttpHandler的ProcessRequest(非同步就是BeginProcessRequest)就用來執行請求返回輸出結果的。如果只需要一種處理常式我們也可以實現IHttpHandler。IHandlerFactory的定義如下:

public interface IHttpHandlerFactory{ IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated); void ReleaseHandler(IHttpHandler handler);} 

  所以,ajaxpro的所有請求都會符合ajaxpro/*.ashx格式,然後在GetHandler方法,就可以進行具體的處理,返回結果是IHttpHandler;以非非同步狀態為例,如果我們配置了需要Session,就會產生一個實現IHttpHandler和IRequiresSessionState的Handler,如果需要唯讀Session,就會產生一個實現IHttpHandler和IReadOnlySessionState的Handler;這些資訊可以通過反射從AjaxMethod標記屬性獲得。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請求,擷取前面的那4個指令碼  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)    {     //擷取標記方法的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,根據是否非同步,session狀態返回指定的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. 反射執行方法

  當獲得一個處理本次請求的Handler後,就可以在其ProcessRequest(非同步為BeginProcessRequest)執行指定的方法。要執行一個頁面對象的方法,我們必須知道指定頁面所在的程式集,名稱空間,頁面類的名稱以及方法的名稱。這似乎符合我們前面:名稱空間.類名稱.方法名稱的調用方式。為了與一般請求區分開,讓組件具有足夠的獨立性,ajaxpro只攔截符合"ajaxpro/*.ashx格式的請求,這說明我們的ajax請求也要符合這個格式。如:http://localhost:50712/ajaxpro/AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode.ashx,這個格式由前台指令碼自動產生,並不需要我們去構造。仔細觀察,會發現AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode 就是頁面類的完全限定名:名稱空間.類名稱,程式集名稱,通過這個我們就可以產生具體的Type,然後進行反射擷取資訊。那麼方法的名稱呢?ajaxpro將其放在http header 中,名稱為:X-AjaxPro-Method。有了這些資訊,就可以反射執行方法了。這裡核心代碼為:

internal void Run(){ try {  //設定輸出結果不緩衝(這不一定是我們想要的)  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;  //驗證ajax請求  if (!p.IsValidAjaxToken())  {   p.SerializeObject(new System.Security.SecurityException("The AjaxPro-Token is not valid."));   return;  }  //方法參數對象數組  object[] po = null;  //請求處理結果  object res = null;  try  {   //擷取參數   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)   {    //使用反射調用靜態方法    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    {     //建立執行個體對象,反射調用執行個體方法     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  {   //判斷結果是不是xml,如是設定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){}   //如果需要緩衝,則將結果寫入緩衝   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){}}

三、總結

  我們總結一下ajaxpro的核心處理流程,它通過一個IHttpHandlerFactory攔截指定格式的url,然後從中擷取類型的完全限定名組建類型對象,接著通過反射擷取標記方法的特性,產生一個自訂的實現IHttpHandler介面的對象;在其ProcessRequest方法中,從http headers擷取方法名稱,通過反射進行參數映射並執行函數。

  ajaxpro 具有如下優點:

  1. 配置簡單。

  2. 可以配合其它組件一起使用。

  3. 封裝前台指令碼,我們不用自己封裝或者使用其它指令碼庫。

  4. 對傳回值處理,我們可以返回簡單類型或者複雜類型都會自動序列化。  

  缺點是:

  1. 頁面會多出4個請求。儘管會利用304緩衝,但還是需要發送請求到伺服器。

  2. ajax無法使用Get請求。由於自訂了url格式,使用這種格式就無法用Get請求了,我們知道Get請求是可以被瀏覽器緩衝的,雅虎前端最佳化建議中有一條就是多用get請求。事實上,應該把名稱空間.類名稱,程式集放到http header中,然後提供了一個type類型的參數讓我們自由選擇。

  3. 與<form runat="server">綁定。目的是用了為我們產生前台指令碼,但如果我們希望用.html檔案 + .aspx.cs 的方式就不能用了(部落格園有些頁面就用了這種方式);甚至我們的介面可能要給移動端使用,這種方便就變成了限制。

  4. 反射。這樣效率是比較低的,它甚至沒有像我們之前的頁面類一樣,對MethodInfo進行緩衝。

  可以看出,如果在不太計較效率的情況,這個組件還是值得使用的。這裡只是做一個核心的介紹,裡面還有很多其它功能,這是ajaxpro組件的原始碼,有興趣的朋友可以研究研究。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.