編寫輕量ajax組件03-實現(附源碼)

前言html

  經過前兩篇的介紹,咱們知道要執行頁面對象的方法,核心就是反射,是從請求獲取參數並執行指定方法的過程。實際上這和asp.net mvc框架的核心思想很相似,它會解析url,從中獲取controller和action名稱,而後激活controller對象,從請求獲取action參數並執action。在web form平臺上,咱們把方法寫在.aspx.cs中,要實現的就是在頁面對象還未生成的狀況下,執行指定的方法,而後返回結果。web

  咱們先看實現後幾個調用例子,這些功能也能夠組合使用:ajax

        [AjaxMethod]
        public void Test1(int index)
        {
            //簡單調用
        }

        [AjaxMethod]
        public string Test2(Test test)
        {
            return "參數爲一個Test實例";
        }

        [AjaxMethod(OutputCache = 20)]
        public string Test3(int index)
        {
            return "輸出結果緩存20秒";
        }

        [AjaxMethod(ServerCache = 20)]
        public string Test4()
        {
            return "在服務端緩存20秒";
        }

        [AjaxMethod(SessionState=SessionState.None)]
        public void Test5()
        {
           //Session未被加載
        }

        [AjaxMethod(SessionState = SessionState.ReadOnly)]
        public void Test6()
        {
            //Session只能讀不能寫
        }

        [AjaxMethod(SessionState = SessionState.ReadWrite)]
        public void Test7()
        {
            //Session能夠讀寫
        }

        [AjaxMethod(IsAsync = true)]
        public void Test8()
        {
            //異步調用
        }   

  前面兩篇咱們已經熟悉基本的執行流程,如今直接進入主題。sql

Ajax約定數據庫

  一般如今主流瀏覽器在使用ajax發送異步請求時,請求頭都會帶上一個:X-Requested-With:XMLHttpRequest 的標記。咱們也能夠直接經過這個標記來判斷是否是ajax請求,不過項目中可能有用其它的組件,爲了避免相互影響,咱們加入一個自定義的請求頭。這裏爲:json

    internal static class AjaxConfig
    {
        /// <summary>
        /// 請求頭Ajax標記鍵
        /// </summary>
        public const string Key = "AjaxFlag";

        /// <summary>
        /// 請求頭Ajax標記值
        /// </summary>
        public const string Value = "XHR";

        /// <summary>
        /// 請求頭Ajax方法標記
        /// </summary>
        public const string MethodName = "";
    }
View Code

  意思是若是http 的請求頭包含一個 AjaxFlag : XHR,就是咱們要處理的。另外http header的MethodName就表示咱們要執行的方法的名稱。數組

AjaxMethodAttribute標記屬性瀏覽器

  標記屬性是給反射用的,在這裏定義咱們須要的一些功能。咱們但願有:緩存

  1. 能夠配置Session狀態session

  2. 支持異步Handler

  3. 支持Get緩存

  4. 支持服務端緩存

  定義以下,用AttributeUsag標記該標記只能用於方法上。

    /// <summary>
    /// ajax方法標記屬性
    /// </summary>
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public class AjaxMethodAttribute : Attribute
    {
        public AjaxMethodAttribute()
        {           
        }

        private SessionState _sessionState = SessionState.None;
        private int _outputCache = 0;
        private int _serverCache = 0;
        private ContentType _contentType = ContentType.Plain;
        private bool _isUseAsync = false;

        /// <summary>
        /// session狀態
        /// </summary>
        public SessionState SessionState 
        {
            get { return _sessionState; }
            set { _sessionState = value; }
        }

        /// <summary>
        /// 客戶端緩存時間,以秒爲單位。該標記只對get請求有效
        /// </summary>
        public int OutputCache 
        {
            get { return _outputCache; }
            set { _outputCache = value; }
        }

        /// <summary>
        /// 服務端緩存時間,以秒爲單位
        /// </summary>
        public int ServerCache 
        {
            get { return _serverCache; }
            set { _serverCache = value; }
        }        

        /// <summary>
        /// 輸出類型(默認爲text/plain)
        /// </summary>
        public ContentType ContentType 
        {
            get { return _contentType; }
            set { _contentType = value; }
        }

        /// <summary>
        /// 使用啓用異步處理
        /// </summary>
        public bool IsAsync 
        {
            get { return _isUseAsync; }
            set { _isUseAsync = value; }
        }
    }

    /// <summary>
    /// Session狀態
    /// </summary>
    public enum SessionState
    {
        None,
        ReadOnly,
        ReadWrite        
    }

    /// <summary>
    /// 輸出內容類型
    /// </summary>
    public enum ContentType
    {
        Plain,
        Html,
        XML,
        Javascript,
        JSON
    }
View Code

各類處理程序和AjaxHandlerFactory

  按照上一篇的說法,具體的Handler主要分爲兩類,異步和非異步;這兩類下,對於Session的狀態又有3三種,不支持、只支持讀(實現IReadOnlySessionState接口)、支持讀寫(實現IRequiresSessionState接口)。IReadOnlySessionState和IRequiresSessionState都只是標記接口(無任何方法,其實應該用標記屬性實現比較合理)。異步的Handler須要實現IHttpAsyncHandler接口,該接口又實現了IHttpHandler。Handler的ProcessRequest方法(或BeginProcessRequest)就是咱們要執行方法的地方。定義以下:

  非異步狀態的Handler:

    //不支持Session
    internal class SyncAjaxHandler : IHttpHandler
    {
        private Page _page;
        private CacheMethodInfo _cacheMethodInfo;

        internal SyncAjaxHandler(Page page, CacheMethodInfo cacheMethodInfo)
        {
            _page = page;
            _cacheMethodInfo = cacheMethodInfo;
        }

        public void ProcessRequest(HttpContext context)
        {
            //執行方法(下面詳細介紹)
            Executor.Execute(_page, context, _cacheMethodInfo);
        }

        public bool IsReusable
        {
            get { return false; }
        }

        public static SyncAjaxHandler CreateHandler(Page page, CacheMethodInfo cacheMethodInfo, SessionState state)
        {
            switch (state)
            {
                case SessionState.ReadOnly:
                    return new SyncAjaxSessionReadOnlyHandler(page, cacheMethodInfo);
                case SessionState.ReadWrite:
                    return new SyncAjaxSessionHandler(page, cacheMethodInfo);
                default:
                    return new SyncAjaxHandler(page, cacheMethodInfo);
            }
        }
    }

    //支持只讀Session
    internal class SyncAjaxSessionReadOnlyHandler : SyncAjaxHandler, IReadOnlySessionState
    {
        internal SyncAjaxSessionReadOnlyHandler(Page page, CacheMethodInfo cacheMethodInfo)
            : base(page, cacheMethodInfo)
        {
        }
    }

    //支持讀寫Session
    internal class SyncAjaxSessionHandler : SyncAjaxHandler, IRequiresSessionState
    {
        internal SyncAjaxSessionHandler(Page page, CacheMethodInfo cacheMethodInfo)
            : base(page, cacheMethodInfo)
        {
        }
    }  
View Code

  異步狀態的Handler:

    //不支持Session
    internal class ASyncAjaxHandler : IHttpAsyncHandler, IHttpHandler
    {
        private Page _page;
        private CacheMethodInfo _cacheMethodInfo;

        internal ASyncAjaxHandler(Page page, CacheMethodInfo cacheMethodInfo)
        {
            _page = page;
            _cacheMethodInfo = cacheMethodInfo;
        }

        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            //執行方法(下面詳細介紹)
            Action<Page, HttpContext, CacheMethodInfo> action = new Action<Page, HttpContext, CacheMethodInfo>(Executor.Execute);
            IAsyncResult result = action.BeginInvoke(_page, context, _cacheMethodInfo, cb, action);
            return result;
        }

        public void EndProcessRequest(IAsyncResult result)
        {
            Action<Page, HttpContext, CacheMethodInfo> action = result.AsyncState as Action<Page, HttpContext, CacheMethodInfo>;
            action.EndInvoke(result);
        }

        public void ProcessRequest(HttpContext context)
        {
            throw new NotImplementedException();
        }

        public bool IsReusable
        {
            get { return false; }
        }

        public static ASyncAjaxHandler CreateHandler(Page page, CacheMethodInfo cacheMethodInfo, SessionState state)
        {
            switch (state)
            {
                case SessionState.ReadOnly:
                    return new ASyncAjaxSessionReadOnlyHandler(page, cacheMethodInfo);
                case SessionState.ReadWrite:
                    return new ASyncAjaxSessionHandler(page, cacheMethodInfo);
                default:
                    return new ASyncAjaxHandler(page, cacheMethodInfo);
            }
        }
    }

    //支持只讀Session
    internal class ASyncAjaxSessionReadOnlyHandler : ASyncAjaxHandler, IReadOnlySessionState
    {
        internal ASyncAjaxSessionReadOnlyHandler(Page page, CacheMethodInfo cacheMethodInfo)
            : base(page, cacheMethodInfo)
        {
        }
    }

    //支持讀寫Session
    internal class ASyncAjaxSessionHandler : ASyncAjaxHandler, IRequiresSessionState
    {
        internal ASyncAjaxSessionHandler(Page page, CacheMethodInfo cacheMethodInfo)
            : base(page, cacheMethodInfo)
        {
        }
    }    
View Code

  AjaxHandlerFactory實現了IHandlerFactory接口,用來根據請求生成具體的Handler,它須要在web.config進行註冊使用。AjaxHandlerFactory的GetHandler是咱們攔截請求的第一步。經過請求頭的AjaxFlag:XHR來判斷是否須要咱們處理,若是是,則建立一個Handler,不然按照普通的方式進行。因爲咱們的方法是寫在.aspx.cs內的,咱們的請求是.aspx後綴的,也就是頁面(Page,實現了IHttpHandler)類型,Page是經過PageHandlerFactory建立的,PageHandlerFactory也實現了IHandlerFactory接口,表示它是用來建立處理程序的。因此咱們須要用PageHandlerFactory來建立一個IHttpHandler,不過PageHandlerFactory的構造函數是protected internal類型的,咱們沒法直接new一個,因此須要經過一個CommonPageHandlerFactory繼承它來實現。

  經過PageHandlerFactory得到Page後,結合方法名稱,咱們就能夠反射獲取AjaxMethodAttribute標記屬性了。而後根據它的相關屬性生成具體的Handler。具體代碼以下:

    internal class CommonPageHandlerFactory : PageHandlerFactory { }

    internal class AjaxHandlerFactory : IHttpHandlerFactory
    {
        public void ReleaseHandler(IHttpHandler handler)
        {
        }

        public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
        {
            HttpRequest request = context.Request;
            if (string.Compare(request.Headers[AjaxConfig.Key], AjaxConfig.Value, true) == 0)
            {
                //檢查函數標記
                string methodName = request.Headers[AjaxConfig.MethodName];
                if (methodName.IsNullOrEmpty())
                {
                    Executor.EndCurrentRequest(context, "方法名稱未正確指定!");
                    return null;
                }
                try
                {                    
                    CommonPageHandlerFactory ajaxPageHandler = new CommonPageHandlerFactory();
                    IHttpHandler handler = ajaxPageHandler.GetHandler(context, requestType, url, pathTranslated);
                    Page page = handler as Page;
                    if (page == null)
                    {
                        Executor.EndCurrentRequest(context, "處理程序類型必須是aspx頁面!");
                        return null;
                    }
                    return GetHandler(page, methodName, context);
                }
                catch
                {
                    Executor.EndCurrentRequest(context, url + " 不存在!");
                    return null;
                }
            }
            if (url.EndsWith(".aspx", StringComparison.CurrentCultureIgnoreCase))
            {
                CommonPageHandlerFactory orgPageHandler = new CommonPageHandlerFactory();
                return orgPageHandler.GetHandler(context, requestType, url, pathTranslated);
            }
            return null;
        }

        /// <summary>
        /// 獲取自定義處理程序
        /// </summary>
        /// <param name="page">處理頁面</param>
        /// <param name="methodName">處理方法</param>
        /// <param name="context">當前請求</param>
        private IHttpHandler GetHandler(Page page, string methodName, HttpContext context)
        {
            //根據Page和MethodName進行反射,獲取標記屬性(下面詳細介紹)
            CacheMethodInfo methodInfo = Executor.GetDelegateInfo(page, methodName);
            if (methodInfo == null)
            {
                Executor.EndCurrentRequest(context, "找不到指定的Ajax方法!");
                return null;
            }
            AjaxMethodAttribute attribute = methodInfo.AjaxMethodAttribute;
            if (attribute.ServerCache > 0)
            {
                //先查找緩存
                object data = CacheHelper.TryGetCache(context);
                if (data != null)
                {
                    Executor.EndCurrentRequest(context, data);
                    return null;
                }
            }
            if (attribute.IsAsync)
            {
                //異步處理程序
                return ASyncAjaxHandler.CreateHandler(page, methodInfo, attribute.SessionState);
            }
            return SyncAjaxHandler.CreateHandler(page, methodInfo, attribute.SessionState);
        }
    }
View Code

  上面的CacheMethodInfo是用於緩存調用方法的相關信息的,第一篇咱們有提到過優化緩存的一些方法,其中就包括緩存+委託。但這裏咱們並不直接緩存方法的MethodInfo,由於緩存MethodInfo的話,須要經過Invoke去執行,這樣的效率比較低。這裏我緩存的是方法的委託,該委託的簽名爲:Func<object, object[], object>,該委託的返回值爲object類型,表示能夠返回任意的類型(咱們能夠在組件內部進行處理,例如若是是引用類型(非string),就將其序列化爲json,但這裏並無實現)。該委託接收兩個參數,第一個參數是方法所屬的對象,若是是靜態方法就是null;第二個參數是方法的參數,定義爲object[]表示能夠接收任意類型的參數。經過委託執行方法,與直接調用方法的效率差異就不是很大(對委託不熟悉的朋友能夠參見:委託)。CacheMethodInfo的定義以下:

    /// <summary>
    /// 緩存方法信息    
    /// </summary>
    sealed class CacheMethodInfo
    {
        /// <summary>
        /// 方法名稱
        /// </summary>
        public string MethodName { get; set; }

        /// <summary>
        /// 方法委託
        /// </summary>
        public Func<object, object[], object> Func { get; set; }

        /// <summary>
        /// 方法參數
        /// </summary>
        public ParameterInfo[] Parameters { get; set; }

        /// <summary>
        /// Ajax標記屬性
        /// </summary>
        public AjaxMethodAttribute AjaxMethodAttribute { get; set; }
    }
View Code

核心方法

1. Eexcutor.GetDelegateInfo 獲取方法相關信息

  該方法用於遍歷頁面類,獲取全部AjaxMethodAttribute標記的方法信息,生成一個CacheMethodInfo對象,包括標記信息、方法名稱、參數信息,以及最重要的方法委託。該對象會緩存在一個哈希表中,下次獲取時,直接從內存得到。

        /// <summary>
        /// 獲取頁面標記方法信息
        /// </summary>
        /// <param name="page">頁面對象</param>
        /// <param name="methodName">方法名稱</param>
        internal static CacheMethodInfo GetDelegateInfo(Page page, string methodName)
        {
            if (page == null)
            {
                throw new ArgumentNullException("page");
            }
            Type type = page.GetType();
            //ajaxDelegateTable是一個Hashtable
            Dictionary<string, CacheMethodInfo> dic = ajaxDelegateTable[type.AssemblyQualifiedName] as Dictionary<string, CacheMethodInfo>;
            if (dic == null)
            {
                dic = new Dictionary<string, CacheMethodInfo>();
                //遍歷頁面的全部MethodInfo
                IEnumerable<CacheMethodInfo> infos = (from m in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
                                                      let ca = m.GetCustomAttributes(typeof(AjaxMethodAttribute), false).FirstOrDefault()
                                                      where ca != null
                                                      select new CacheMethodInfo
                                                      {
                                                          //方法標記屬性
                                                          AjaxMethodAttribute = ca as AjaxMethodAttribute,
                                                          //方法名稱
                                                          MethodName = m.Name,
                                                          //方法參數信息
                                                          Parameters = m.GetParameters()
                                                      });

                if (infos.IsNullOrEmpty())
                {
                    return null;
                }                    
                for (int i = 0, length = infos.Count(); i < length; i++)
                {                    
                    CacheMethodInfo cacheMethodInfo = infos.ElementAt(i);                    
                    string name = cacheMethodInfo.MethodName;
                    MethodInfo methodInfo = type.GetMethod(name);
                    if (!dic.ContainsKey(name))
                    {                        
                        //根據MethodInfo獲取方法委託
                        cacheMethodInfo.Func = ReflectionUtil.GetMethodDelegate(methodInfo);
                        dic.Add(name, cacheMethodInfo);
                    }
                }
                ajaxDelegateTable[type.AssemblyQualifiedName] = dic;
            }
            CacheMethodInfo currentMethodInfo = null;
            dic.TryGetValue(methodName, out currentMethodInfo);
            return currentMethodInfo;            
        }
View Code

  獲取方法的委託的是經過一個ReflectionUtil得到的,該類主要用來優化反射,它經過Expression,能夠將MethodInfo編譯成Func<object,object[],object>委託,爲Type編譯一個Func<object>委託,用於建立實例對象。

  經過Expression優化反射

  Expression(表達式樹)容許咱們將代碼邏輯以表達式的形式存儲在樹狀結構裏,而後在運行時去動態解析,實現動態編輯和執行代碼。熟悉ORM框架的朋友對Expression確定很熟悉,由於大部分方法都有一個Expression<TDelegate>類型的參數。訪問關係型數據庫的本質仍是sql語句,orm的工做就是爲開發人員屏蔽這個過程,以面向對象的方式去讀寫數據庫,而不是本身編寫sql語句。例如,Users.Where(u => u.Age > 18) 就可查詢年齡大於18的用戶。這裏不對應用在orm的過程進行詳解,下面咱們介紹如何用Expression並利用它來生成委託。

  .net定義了許多表達式類型,這些類型都派生自Expression,Expression是一個抽象類,並且是一個工廠類,全部類型的表達式都經過它來建立。如圖:

  先看一個 1 * 2 + 2 例子,咱們用表達樹來描述來描述它:

            /*
             *  a * b + 2 
             */

            /*
            直接操做
            int a = 1, b = 2;
            int result = a * 2 + 2;
            */

            /*
            經過委託調用
            Func<int, int, int> func = new Func<int, int, int>((a, b) => { return a * b + 2; });
            func(1, 2);
            */

            /*經過Expression調用*/

            //定義兩個參數
            ParameterExpression pe1 = Expression.Parameter(typeof(int), "a");
            ParameterExpression pe2 = Expression.Parameter(typeof(int), "b");
            //定義一個常量
            ConstantExpression constExpression = Expression.Constant(2);            

            //參數數組
            ParameterExpression[] parametersExpression = new ParameterExpression[]{pe1,pe2};

            //一個乘法運算
            BinaryExpression multiplyExpression = Expression.Multiply(pe1, pe2);

            //一個加法運算
            BinaryExpression unaryExpression = Expression.Add(multiplyExpression, constExpression);

            //將上面的表達式轉換爲一個委託表達式
            LambdaExpression lambdaExpression = Expression.Lambda<Func<int, int, int>>(unaryExpression, parametersExpression);

            //將委託編譯成可執行代碼
            Func<int,int,int> func = lambdaExpression.Compile() as Func<int,int,int>;
            Console.WriteLine(func(1, 2));
View Code

  能夠看到咱們最終將其編譯爲一個具體類型的委託了。下面看咱們真正用到的方法是如何實現的,代碼以下:

        public static Func<object, object[], object> GetMethodDelegate(MethodInfo methodInfo)
        {
            if (methodInfo == null)
            {
                throw new ArgumentNullException("methodInfo");
            }
            //定義參數表達式,它表示委託的第一個參數
            ParameterExpression instanceExp = Expression.Parameter(typeof(object), "instance");

            //定義參數表達式,它表示委託的第二個參數
            ParameterExpression paramExp = Expression.Parameter(typeof(object[]), "parameters");

            //獲取方法的參數信息數組
            ParameterInfo[] paramInfos = methodInfo.GetParameters();

            //參數表達式集合
            List<Expression> paramExpList = new List<Expression>();

            int length = paramInfos.Length;
            for (int i = 0; i < length; i++)
            {
                //獲取paramExp參數數組的第i個元素
                BinaryExpression valueObj = Expression.ArrayIndex(paramExp, Expression.Constant(i));

                //將其轉換爲與參數類型一致的類型
                UnaryExpression valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType);

                //添加到參數集合
                paramExpList.Add(valueCast);
            }     

            //方法所屬的實例的表達式,若是爲靜態則爲null
            UnaryExpression instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceExp, methodInfo.ReflectedType);

            //表示調用方法的表達式 
            MethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, paramExpList);

            //將表達式目錄描述的lambda編譯爲可執行代碼(委託)
            if (methodCall.Type == typeof(void))
            {
                Expression<Action<object, object[]>> lambda = Expression.Lambda<Action<object, object[]>>(methodCall, instanceExp, paramExp);
                Action<object, object[]> action = lambda.Compile();
                return (instance, parameters) =>
                {
                    action(instance, parameters);
                    return null;
                };
            }
            else
            {
                UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
                Expression<Func<object, object[], object>> lambda = Expression.Lambda<Func<object, object[], object>>(castMethodCall, instanceExp, paramExp);
                return lambda.Compile();
            }
        }
View Code

  具體代碼都有註釋解釋,最終咱們得到了一個Func<object,object[],object>類型的委託,它會做爲CacheMethodInfo的屬性進行緩存。有興趣測試反射性能的朋友,也不妨去測試對比一下這幾種方式執行的效率差異:1.直接執行方法 2.Emit 3. 緩存+委託 4.Delegate.DynamicInvoke。

2. Executor.Execute 執行委託

  在執行委託前,咱們須要先從請求獲取參數,映射到方法。參數能夠是簡單的類型,如 string Test(int i,int j); 也能夠是一個對象,如 string Test(User user); 若是是 string Test(User user1, User user2) 也行,提交參數時只須要加上 user1或 user2 前綴便可,例如 user1.Name,user2.Name。這裏沒有支持更多的匹配方式,像mvc,它還支持嵌套類型等等,這些能夠本身去實現。若是參數是一個對象,咱們可能須要爲它的字段進行賦值,也可能爲它的屬性進行賦值。這裏咱們定義一個DataMember,用來表示字段或屬性的父類。如:

     internal abstract class DataMember
    {
        public abstract string Name { get; }
        public abstract Type MemberType { get; }
        public abstract void SetValue(object instance,object value);
        public abstract object GetValue(object instance);
    }

  接着定義屬性類型PropertyMember和字段類型FieldMember,分別繼承了DataMember。

  PropertyMember定義:

    internal class PropertyMember : DataMember
    {
        private PropertyInfo property;
        public PropertyMember(PropertyInfo property)
        {
            if (property == null)
            {
                throw new ArgumentNullException("property");
            }
            this.property = property;
        }

        public override void SetValue(object instance, object value)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            this.property.SetValue(instance, value, null);
        }

        public override object GetValue(object instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            return this.property.GetValue(instance,null);
        }

        public override string  Name
        {
            get { return this.property.Name; }
        }

        public override Type MemberType
        {
            get { return this.property.PropertyType; }
        }
    }
View Code

  FieldMember定義:

    internal class FieldMember : DataMember
    {
        private FieldInfo field;
        public FieldMember(FieldInfo field)
        {
            if (field == null)
            {
                throw new ArgumentNullException("field");
            }
            this.field = field;
        }

        public override void SetValue(object instance, object value)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            this.field.SetValue(instance, value);
        }

        public override object GetValue(object instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            return this.field.GetValue(instance);
        }

        public override string  Name
        {
            get { return this.field.Name;}
        }

        public override Type MemberType
        {
            get { return this.field.FieldType; }
        }
    }
View Code

  定義一個DataMemberManager,用來遍歷Type,獲取全部字段和屬性的,實現以下:

    internal static class DataMemberManager
    {
        /// <summary>
        /// 獲取實例字段/屬性集合
        /// </summary>
        /// <param name="type">類型</param>
        /// <returns></returns>
        public static List<DataMember> GetDataMember(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }
            IEnumerable<PropertyMember> propertyMembers = from property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                                  select new PropertyMember(property);
            IEnumerable<FieldMember> fieldMembers = from field in type.GetFields(BindingFlags.Instance | BindingFlags.Public)
                                             select new FieldMember(field);
            List<DataMember> members = new List<DataMember>();
            foreach(var property in propertyMembers)
            {
                members.Add(property);
            }
            foreach (var field in fieldMembers)
            {
                members.Add(field);
            }
            return members;
        }
    }
View Code

  在前面咱們定義的Handler的ProcessRequest方法中,咱們調用了Executor.Execute,該方法用於執行委託,實現以下:

        /// <summary>
        /// 核心函數,執行Handler的方法
        /// </summary>
        /// <param name="page">頁面對象</param>
        /// <param name="context">請求上下文</param>
        /// <param name="cacheMethodInfo">緩存方法原數據</param>
        internal static void Execute(Page page, HttpContext context, CacheMethodInfo methodInfo)
        {
            if (page == null)
            {
                throw new ArgumentNullException("page");
            }
            try
            {
                if (methodInfo != null)
                {
                    HttpRequest request = context.Request;
                    object[] parameters = GetParametersFromRequest(request, methodInfo.Parameters);
                    object data = methodInfo.Func(page, parameters);
                    int serverCache = methodInfo.AjaxMethodAttribute.ServerCache;
                    if (serverCache > 0)
                    {
                        CacheHelper.Insert(context, methodInfo.AjaxMethodAttribute.ServerCache, data);
                    }
                    EndCurrentRequest(context, data, methodInfo.AjaxMethodAttribute.OutputCache);
                }
                else
                {
                    EndCurrentRequest(context, "找不到合適的Ajax方法!");
                }
            }
            catch (FormatException)
            {
                EndCurrentRequest(context, "調用方法匹配到無效的參數!");
            }
            catch (InvalidCastException)
            {
                EndCurrentRequest(context, "參數轉換出錯!");
            }
            catch (System.Threading.ThreadAbortException)
            {
                //do nothing
            }
            catch (Exception ex)
            {
                EndCurrentRequest(context, ex.Message);
            }
        }
View Code

  CacheMethodInfo咱們已經得到了,如今只要得到參數咱們就能夠執行方法。

  GetParameterFromRequest用於從請求獲取object[]參數數組。根據上面所說的,若是參數是一個簡單類型,那麼直接進行轉換;若是是實例對象,那麼咱們先要建立new一個實例對象,而後爲其字段或屬性賦值。實現以下:

        /// <summary>
        /// 從請求獲取參參數
        /// </summary>
        /// <param name="request">HttpRequest</param>
        ///<param name="parameters">參數信息</param>
        /// <returns>參數數組</returns>
        private static object[] GetParametersFromRequest(HttpRequest request, ParameterInfo[] parameters)
        {
            if (parameters.IsNullOrEmpty())
            {
                return null;
            }
            int length = parameters.Length;
            object[] realParameters = new object[length];
            for (int i = 0; i < length; i++)
            {
                ParameterInfo pi = parameters[i];
                Type piType = pi.ParameterType.GetRealType();
                object value = null;
                if (piType.IsValueType())
                {
                    //值類型
                    value = ModelUtil.GetValue(request, pi.Name, piType);
                    value = value ?? Activator.CreateInstance(piType);
                }
                else if (piType.IsClass)
                {
                    //引用類型
                    object model = ModelUtil.CreateModel(piType);
                    ModelUtil.FillModelByRequest(request, pi.Name, piType, model);
                    value = model;
                }
                else
                {
                    throw new NotSupportedException(pi.Name + " 參數不被支持");
                }
                realParameters[i] = value;
            }
            return realParameters;
        }
View Code

  ModelUtil會從Http Request獲取參數,並進行類型轉換處理:

    internal static class ModelUtil
    {
        /// <summary>
        /// 緩存構造函數
        /// </summary>
        private static Hashtable constructorTable = Hashtable.Synchronized(new Hashtable());
 
        /// <summary>
        /// 根據名稱從HttpRequest獲取值
        /// </summary>
        /// <param name="request">HttpRequest</param>
        /// <param name="name">鍵名稱</param>
        /// <param name="type">參數類型</param>
        /// <returns></returns>
        public static object GetValue(HttpRequest request, string name, Type type)
        {
            string[] values = null;
            if (string.Compare(request.RequestType, "POST", true) == 0)
            {
                values = request.Form.GetValues(name);
            }
            else
            {
                values = request.QueryString.GetValues(name);
            }
            if (values.IsNullOrEmpty())
            {
                return null;
            }
            string data = values.Length == 1 ? values[0] : string.Join(",", values);
            return Convert.ChangeType(data, type);
        }

        /// <summary>
        /// 建立實例對象
        /// </summary>
        /// <param name="type">實例類型</param>
        /// <returns></returns>
        public static object CreateModel(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }
            Func<object> func = constructorTable[type.AssemblyQualifiedName] as Func<object>;
            if (func == null)
            {   
                func = ReflectionUtil.GetConstructorDelegate(type);
                constructorTable[type.AssemblyQualifiedName] = func;
            }
            if (func != null)
            {
                return func();
            }
            return null;
        }

        /// <summary>
        /// 填充模型
        /// </summary>
        /// <param name="request">HttpRequest</param>
        /// <param name="name">鍵名稱</param>
        /// <param name="prefix">參數類型</param>
        /// <parparam name="model">實例對象</parparam>
        public static void FillModelByRequest(HttpRequest request, string name, Type type, object model)
        {
            if (model == null)
            {
                return;
            }
            IEnumerable<DataMember> members = DataMemberManager.GetDataMember(type);
            if (members.IsNullOrEmpty())
            {
                return;
            }
            object value = null;
            foreach (DataMember member in members)
            {
                value = GetValue(request, string.Format("{0}.{1}", name, member.Name), member.MemberType);
                value = value ?? GetValue(request, member.Name, member.MemberType);
                member.SetValue(model, value);
            }
        }
    }
View Code

  若是是引用類型,須要經過構造函數建立對象,像前面用於,這裏咱們也用Expression來構建一個Func<object>類型的委託來優化,它調用了ReflectionUtil.GetConstructorDelegate方法。實現以下:

        /// <summary>
        /// 獲取構造函數委託
        /// </summary>
        /// <param name="type">實例類型</param>
        /// <returns></returns>
        public static Func<object> GetConstructorDelegate(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }
            ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
            if (ci == null)
            {
                throw new MissingMemberException("類型必須有一個無參public構造函數!");
            }
            NewExpression newExp = Expression.New(type);
            Expression<Func<object>>  lambda = Expression.Lambda<Func<object>>(newExp);
            return lambda.Compile();
        }
View Code
  最後再輸出結果時,若是是Get請求,而且須要緩存,咱們還須要設置一下Response.Cache。以下:
        private static void EndRequest(HttpContext context, object data, int outPutCache, ContentType contentType)
        {
            HttpResponse response = context.Response;
            if (outPutCache != 0)
            {
                if (string.Compare(context.Request.HttpMethod, "GET", true) == 0)
                {
                    if (outPutCache > 0)
                    {
                        response.Cache.SetCacheability(HttpCacheability.Public);
                        response.Cache.SetMaxAge(new TimeSpan(0, 0, outPutCache));
                        response.Cache.SetExpires(DateTime.Now.AddSeconds(outPutCache));
                    }
                    else
                    {
                        response.Cache.SetCacheability(HttpCacheability.NoCache);
                        response.Cache.SetNoStore();
                    }
                }
            }
            response.ContentType = GetContentType(contentType);
            response.ContentEncoding = System.Text.Encoding.UTF8;
            if (data != null)
            {
                response.Write(data);
            }
            response.End();
        }
View Code
總結
  如今無論咱們前臺用什麼腳本庫,只要按照約定就能夠調用標記方法。上面已經介紹了組件的核心部分,您也能夠按照本身的想法進行擴展,也歡迎共同窗習交流。
   源碼下載
相關文章
相關標籤/搜索