編寫輕量ajax組件01-與webform平台上的各種實現方式比較_AJAX相關

來源:互聯網
上載者:User

前言

  Asp.net WebForm 和 Asp.net MVC(簡稱MVC) 都是基於Asp.net的web開發架構,兩者有很大的區別,其中一個就是MVC更加註重http本質,而WebForm試圖屏蔽http,為此提供了大量的伺服器控制項和ViewState機制,讓開發人員可以像開發Windows Form應用程式一樣,基於事件模型去編程。兩者各有優缺點和適用情景,但MVC現在是許多Asp.net開發人員的首選。

  WebForm是建立在Asp.net的基礎上的,Asp.net提供了足夠的擴充性,我們也可以利用這些在WebForm下編寫像MVC一樣的架構,這個有機會再寫。說到WebForm很多人就會聯想到伺服器控制項(拖控制項!!!),其實不然,我們也可以完全不使用伺服器控制項,像MVC那樣關注html。WebForm要拋棄伺服器控制項,集中關注html,首先就要將<form runat="server"></form>標籤去掉,這個runat server 的form 是其PostBack機制的基礎。既然我們要迴歸到html+css+js,那麼意味著許多東西都要自己實現,例如處理Ajax請求。不像MVC那樣,WebForm開始的設計就將伺服器控制項作為主要組成部分,如果不使用它,那麼只能利用它的擴充性去實現。

  本系列就是實現一個基於WebForm平台的輕量級ajax組件,主要分為三個部分:

  1. 介紹WebForm下各種實現方式。

  2. 分析ajaxpro組件。

  3. 編寫自己的ajax組件。

一、Ajax簡介

  非同步允許我們在不重新整理整個頁面的情況下,像伺服器請求或提交資料。對於複雜的頁面,為了請求一點資料而重載整個頁面顯然是很低效的,ajax就是為瞭解決這個問題的。ajax的核心是XmlHttpRequest對象,通過該對象,以文本的形式向伺服器提交請求。XmlHttpRequest2.0後,還支援提交位元據。

  ajax安全:出於安全考慮,ajax受同源策略限制;也就是只能訪問同一個域、同一個連接埠的請求,跨域請求會被拒絕。當然有時候需求需要跨域發送請求,常用的跨域處理方法有CORS(跨域資源共用)和JSONP(參數式JSON)。

  ajax資料互動格式:雖然Ajax核心對象XmlHttpRequest有"XML"字眼,但用戶端與伺服器資料交換格式不局限於xml,例如現在更多是使用json格式。  

  ajax 也是有缺點的。例如對搜尋引擎的支援不太好;有時候也會違背url資源定位的初衷。

二、Asp.net MVC 平台下使用ajax

  在MVC裡,ajax調用後台方法非常方便,只需要指定Action的名稱即可。

  前台代碼:

<body>  <h1>index</h1>  <input type="button" value="GetData" onclick="getData()" />  <span id="result"></span></body><script type="text/javascript">  function getData() {    $.get("GetData", function (data) {      $("#result").text(data);    });  }</script>

  後台代碼:

public class AjaxController : Controller{  public ActionResult GetData()  {    if(Request.IsAjaxRequest())    {      return Content("data");    }    return View();  }}

三、WebForm 平台下使用ajax

  3.1 基於伺服器控制項包或者第三方組件

  這是基於伺服器控制項的,例如ajax toolkit工具包,或者像FineUI這樣的組件。web前端始終是由html+css+js組成的,只不過如何去產生的問題。原生的我們可以自己編寫,或者用一些前端外掛程式;基於伺服器控制項的,都是在後台產生的,通常效率也低一點。伺服器組件會在前台產生一系列代理,本質還是一樣的,只不過控制項封裝了這個過程,不需要我們自己編寫。基於控制項或者第三方組件的模式,在一些管理系統還是挺有用的,訪問量不是很大,可以快速開發。

  3.2 基於ICallbackEventHandler介面

  .net 提供了ICallbackEventHandler介面,用於處理回調請求。該介面需要用ClientScriptManager在前台組建代理程式指令碼,用於發送和接收請求,所以需要<form runat="server">標籤。

  前台代碼:

<body>  <form id="form1" runat="server">  <div>        <input type="button" value="擷取回調結果" onclick="callServer()" />    <span id="result" style="color:Red;"></span>  </div>  </form></body><script type="text/javascript">  function getCallbackResult(result){    document.getElementById("result").innerHTML = result;  }</script>

  後台代碼:

public partial class Test1 : System.Web.UI.Page, ICallbackEventHandler{      protected void Page_Load(object sender, EventArgs e)  {    //用戶端指令碼Manager    ClientScriptManager scriptMgr = this.ClientScript;     //擷取回呼函數,getCallbackResult就是回呼函數    string functionName = scriptMgr.GetCallbackEventReference(this, "", "getCallbackResult", "");     //發起請求的指令碼,callServer就是點擊按鈕事件的執行函數    string scriptExecutor = "function callServer(){" + functionName + ";}";     //註冊指令碼    scriptMgr.RegisterClientScriptBlock(this.GetType(), "callServer", scriptExecutor, true);  }   //介面方法  public string GetCallbackResult()  {    return "callback result";  }   //介面方法  public void RaiseCallbackEvent(string eventArgument)  {  }}

  這種方式有以下缺點

  1. 實現起來較複雜,每個頁面Load事件都要去註冊相應的指令碼。

  2. 前台會產生一個用於代理的指令檔。

  3. 對於頁面互動複雜的,實現起來非常麻煩。

  4. 雖然是回調,但是此時頁面對象還是產生了。

  3.3 使用一般處理常式

  一般處理常式其實是一個實現了IHttpHandler介面類,與頁面類一樣,它也可以用於處理請求。一般處理常式通常不用於產生html,也沒有複雜的事件機制,只有一個ProcessRequest入口用於處理請求。我們可以將ajax請求地址寫成.ashx檔案的路徑,這樣就可以處理了,而且效率比較高。

  要輸出常值內容只需要Response.Write(data)即可,例如,從資料庫擷取資料後,序列化為json格式字串,然後輸出。前面說到,一般處理常式不像頁面一樣原來產生html,如果要產生html,可以通過載入使用者控制項產生。如:

public void ProcessRequest(HttpContext context){  Page page = new Page();  Control control = page.LoadControl("~/PageOrAshx/UserInfo.ascx");  if (control != null)  {    StringWriter sw = new StringWriter();    HtmlTextWriter writer = new HtmlTextWriter(sw);    control.RenderControl(writer);    string html = sw.ToString();    context.Response.Write(html);          }}

  這種方式的優點是輕量、高效;缺點是對於互動多的需要定義許多ashx檔案,加大了管理和維護成本。

  3.4 頁面基類

  將處理ajax請求的方法定義在頁面對象內,這樣每個頁面就可以專註處理本頁面相關的請求了。這裡有點需要注意。

  1.如何知道這個請求是ajax請求?

    通過請求X-Requested-With:XMLHttlRequest 可以判斷,大部份瀏覽器的非同步請求都會包含這個要求標頭;也可以通過自訂要求標頭實現,例如:AjaxFlag:XHR。

  2.在哪裡統一處理?

    如果在每個頁面類裡判斷和調用是很麻煩的,所以將這個處理過程轉到一個頁面基類裡處理。

  3.如何知道調用的是哪個方法?

    通過傳參或者定義在要求標頭都可以,例如:MethodName:GetData。

  4.知道方法名稱了,如何動態調用?

    反射。

  5.如何知道該方法可以被外部調用?

    可以認為public類型的就可以被外部調用,也可以通過標記屬性標記。

  通過上面的分析,簡單實現如下  

  頁面基類:

public class PageBase : Page{  public override void ProcessRequest(HttpContext context)  {    HttpRequest request = context.Request;    if (string.Compare(request.Headers["AjaxFlag"],"AjaxFlag",0) == 0)    {      string methodName = request.Headers["MethodName"];      if (string.IsNullOrEmpty(methodName))      {        EndRequest("MethodName標記不可為空!");      }      Type type = this.GetType().BaseType;      MethodInfo info = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);      if (info == null)      {        EndRequest("找不到合適的方法調用!");      }              string data = info.Invoke(this, null) as string;      EndRequest(data);    }    base.ProcessRequest(context);  }  private void EndRequest(string msg)  {    HttpResponse response = this.Context.Response;    response.Write(msg);    response.End();  }}

  頁面類:

public partial class Test1 : PageBase{  protected void Page_Load(object sender, EventArgs e)  {  }  public string GetData()  {    return "213";  }}

  前台代碼:

function getData(){  $.ajax({    headers:{"AjaxFlag":"XHR","MethodName":"GetData"},    success:function(data){      $("#result").text(data);    }  });}

四、最佳化版頁面基類

  上面的頁面基類功能很少,而且通過反射這樣調用的效率很低。這裡最佳化一下:

  1.可以支援簡單類型的參數。

    例如上面的GetData可以是:GetData(string name),通過函數中繼資料可以擷取相關的參數,再根據請求的參數,就可以設定參數了。

  2.加入標記屬性。

    只有被AjaxMethodAttribute標記的屬性才能被外部調用。

  3.最佳化反射。

    利用緩衝,避免每次都根據函數名稱去搜尋函數資訊。

  標記屬性:

public class AjaxMethodAttribute : Attribute{}

  緩衝對象:  

public class CacheMethodInfo{  public string MethodName { get; set; }  public MethodInfo MethodInfo { get; set; }  public ParameterInfo[] Parameters { get; set; }}

  基類代碼:

public class PageBase : Page{  private static Hashtable _ajaxTable = Hashtable.Synchronized(new Hashtable());  public override void ProcessRequest(HttpContext context)  {          HttpRequest request = context.Request;    if (string.Compare(request.Headers["AjaxFlag"],"XHR",true) == 0)    {      InvokeMethod(request.Headers["MethodName"]);    }    base.ProcessRequest(context);  }  /// <summary>  /// 反射執行函數  /// </summary>  /// <param name="methodName"></param>  private void InvokeMethod(string methodName)  {    if (string.IsNullOrEmpty(methodName))    {      EndRequest("MethodName標記不可為空!");    }    CacheMethodInfo targetInfo = TryGetMethodInfo(methodName);    if (targetInfo == null)    {      EndRequest("找不到合適的方法調用!");    }    try    {      object[] parameters = GetParameters(targetInfo.Parameters);      string data = targetInfo.MethodInfo.Invoke(this, parameters) as string;      EndRequest(data);    }    catch (FormatException)    {      EndRequest("參數類型匹配發生錯誤!");    }    catch (InvalidCastException)    {      EndRequest("參數類型轉換髮生錯誤!");    }    catch (ThreadAbortException)    {    }    catch (Exception e)    {      EndRequest(e.Message);    }  }  /// <summary>  /// 擷取函數中繼資料並緩衝  /// </summary>  /// <param name="methodName"></param>  /// <returns></returns>  private CacheMethodInfo TryGetMethodInfo(string methodName)  {    Type type = this.GetType().BaseType;    string cacheKey = type.AssemblyQualifiedName;    Dictionary<string, CacheMethodInfo> dic = _ajaxTable[cacheKey] as Dictionary<string, CacheMethodInfo>;    if (dic == null)    {      dic = new Dictionary<string, CacheMethodInfo>();      MethodInfo[] methodInfos = (from m in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)                    let ma = m.GetCustomAttributes(typeof(AjaxMethodAttribute), false)                    where ma.Length > 0                    select m).ToArray();      foreach (var mi in methodInfos)      {        CacheMethodInfo cacheInfo = new CacheMethodInfo();        cacheInfo.MethodName = mi.Name;        cacheInfo.MethodInfo = mi;        cacheInfo.Parameters = mi.GetParameters();        dic.Add(mi.Name, cacheInfo);      }      _ajaxTable.Add(cacheKey, dic);    }    CacheMethodInfo targetInfo = null;    dic.TryGetValue(methodName, out targetInfo);    return targetInfo;  }  /// <summary>  /// 擷取函數參數  /// </summary>  /// <param name="parameterInfos"></param>  /// <returns></returns>  private object[] GetParameters(ParameterInfo[] parameterInfos)  {    if (parameterInfos == null || parameterInfos.Length <= 0)    {      return null;    }    HttpRequest request = this.Context.Request;    NameValueCollection nvc = null;    string requestType = request.RequestType;    if (string.Compare("GET", requestType, true) == 0)    {      nvc = request.QueryString;    }    else    {      nvc = request.Form;    }    int length = parameterInfos.Length;    object[] parameters = new object[length];    if (nvc == null || nvc.Count <= 0)    {      return parameters;    }    for (int i = 0; i < length; i++)    {      ParameterInfo pi = parameterInfos[i];      string[] values = nvc.GetValues(pi.Name);      object value = null;      if (values != null)      {        if (values.Length > 1)        {          value = String.Join(",", values);        }        else        {          value = values[0];        }      }      if (value == null)      {        continue;      }      parameters[i] = Convert.ChangeType(value, pi.ParameterType);    }          return parameters;  }  private void EndRequest(string msg)  {    HttpResponse response = this.Context.Response;    response.Write(msg);    response.End();  }}

  頁面類:

public string GetData3(int i, double d, string str){  string[] datas = new string[] { i.ToString(), d.ToString(), str };  return "參數分別是:" + String.Join(",", datas);} 

  前台代碼:

function getData3(){  $.ajax({    headers:{"AjaxFlag":"XHR","MethodName":"GetData3"},    data:{"i":1,"d":"10.1a","str":"hehe"},    success:function(data){      $("#result").text(data);    }  });}

五、總結

  上面的頁面基類已經具備可以完成基本的功能,但它還不夠好。主要有:

  1. 依附在頁面基類。對於本來有頁面基類的,無疑會變得更加複雜。我們希望把它獨立開來,變成一個單獨的組件。

  2. 效率問題。反射的效率是很低的,尤其在web這類應用程式上,更應該慎用。以動態執行函數為例,效率主要低在:a.根據字串動態尋找函數的過程。b.執行函數時,反射內部需要將參數打包成一個數組,再將參數解析到線程棧上;在調用前CLR還要檢測參數的正確性,再判斷有沒有許可權執行。上面的最佳化其實只最佳化了一半,也就是最佳化了尋找的過程,而Invoke同樣會有效能損失。當然,隨著.net版本越高,反射的效率也會有所提升,但這種動態東西,始終是用效率換取靈活性的。

  3.不能支援複雜參數。有時候參數比較多,函數參數一般會封裝成一個物件類型。

  4. AjaxMethodAttribute只是一個空的標記屬性。我們可以為它加入一些功能,例如,標記函數的名稱、是否使用Session、緩衝設定等都可以再這裡完成。

  用過WebForm的朋友可能會提到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.