程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 編寫輕量ajax組件02-AjaxPro淺析,ajax02-ajaxpro

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

編輯:C#入門知識

編寫輕量ajax組件02-AjaxPro淺析,ajax02-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文件中必須有一個<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)); 用來注冊所需要的腳本。我們注意到在前台頁面引入了如下腳本:

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組件的源代碼,有興趣的朋友可以研究研究。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved