首頁  >  文章  >  web前端  >  編寫輕量ajax組件02--淺析AjaxPro

編寫輕量ajax組件02--淺析AjaxPro

亚连
亚连原創
2018-05-24 15:01:021653瀏覽

ajaxpro雖然是比較老的元件,但實現想法和原始碼還是很有借鏡價值的。接下來透過本篇文章給大家介紹寫輕量級ajax組件02--淺析AjaxPro,有興趣的朋友可以參考下

前言

  上一篇介紹了在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檔案中必須有一個9da45565527026e0988f6215b7b6a235f5a47148e367a6035fd7a2faa965022e,否則腳本將無法註冊。 (這裡傳遞了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: &#39;/ajaxpro/AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode.ashx&#39;
}));
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接口。

  前面我们进行了如下配置:cf06240b37eddaeaaa5ffa482fe7b198 这表明了任何的以 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. 與9da45565527026e0988f6215b7b6a235綁定。目的是用了為我們產生前台腳本,但如果我們希望用.html檔.aspx.cs 的方式就不能用了(博客園有些頁面就用了這種方式);甚至我們的接口可能要給移動端使用,這種方便就變成了限制。

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

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

上面是我整理給大家的,希望今後對大家有幫助。

相關文章:

淺析json與jsonp區別及透過ajax取得json資料後格式的轉換

Django框架利用ajax實作批次匯入資料功能

AJAX XMLHttpRequest物件詳解

以上是編寫輕量ajax組件02--淺析AjaxPro的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn