編寫輕量ajax組件第三篇實現(xiàn)
通過之前的介紹,我們知道要執(zhí)行頁面對象的方法,核心就是反射,是從請求獲取參數(shù)并執(zhí)行指定方法的過程。實際上這和asp.net mvc框架的核心思想很類似,它會解析url,從中獲取controller和action名稱,然后激活controller對象,從請求獲取action參數(shù)并執(zhí)action。在web form平臺上,我們把方法寫在.aspx.cs中,要實現(xiàn)的就是在頁面對象還未生成的情況下,執(zhí)行指定的方法,然后返回結(jié)果。
我們先看實現(xiàn)后幾個調(diào)用例子,這些功能也可以組合使用:
[AjaxMethod] public void Test1(int index) { //簡單調(diào)用 } [AjaxMethod] public string Test2(Test test) { return "參數(shù)為一個Test實例"; } [AjaxMethod(OutputCache = 20)] public string Test3(int index) { return "輸出結(jié)果緩存20秒"; } [AjaxMethod(ServerCache = 20)] public string Test4() { return "在服務(wù)端緩存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() { //異步調(diào)用 }
前面我們已經(jīng)熟悉基本的執(zhí)行流程,現(xiàn)在直接進入主題。
Ajax約定
通?,F(xiàn)在主流瀏覽器在使用ajax發(fā)送異步請求時,請求頭都會帶上一個:X-Requested-With:XMLHttpRequest 的標(biāo)記。我們也可以直接通過這個標(biāo)記來判斷是不是ajax請求,不過項目中可能有用其它的組件,為了不相互影響,我們加入一個自定義的請求頭。這里為:
internal static class AjaxConfig { /// <summary> /// 請求頭Ajax標(biāo)記鍵 /// </summary> public const string Key = "AjaxFlag"; /// <summary> /// 請求頭Ajax標(biāo)記值 /// </summary> public const string Value = "XHR"; /// <summary> /// 請求頭Ajax方法標(biāo)記 /// </summary> public const string MethodName = ""; }
意思是如果http 的請求頭包含一個 AjaxFlag : XHR,就是我們要處理的。另外http header的MethodName就表示我們要執(zhí)行的方法的名稱。
AjaxMethodAttribute標(biāo)記屬性
標(biāo)記屬性是給反射用的,在這里定義我們需要的一些功能。我們希望有:
1. 可以配置Session狀態(tài)
2. 支持異步Handler
3. 支持Get緩存
4. 支持服務(wù)端緩存
定義如下,用AttributeUsag標(biāo)記該標(biāo)記只能用于方法上。
/// <summary> /// ajax方法標(biāo)記屬性 /// </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狀態(tài) /// </summary> public SessionState SessionState { get { return _sessionState; } set { _sessionState = value; } } /// <summary> /// 客戶端緩存時間,以秒為單位。該標(biāo)記只對get請求有效 /// </summary> public int OutputCache { get { return _outputCache; } set { _outputCache = value; } } /// <summary> /// 服務(wù)端緩存時間,以秒為單位 /// </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狀態(tài) /// </summary> public enum SessionState { None, ReadOnly, ReadWrite } /// <summary> /// 輸出內(nèi)容類型 /// </summary> public enum ContentType { Plain, Html, XML, Javascript, JSON }
各種處理程序和AjaxHandlerFactory
按照上一篇的說法,具體的Handler主要分為兩類,異步和非異步;這兩類下,對于Session的狀態(tài)又有3三種,不支持、只支持讀(實現(xiàn)IReadOnlySessionState接口)、支持讀寫(實現(xiàn)IRequiresSessionState接口)。IReadOnlySessionState和IRequiresSessionState都只是標(biāo)記接口(無任何方法,其實應(yīng)該用標(biāo)記屬性實現(xiàn)比較合理)。異步的Handler需要實現(xiàn)IHttpAsyncHandler接口,該接口又實現(xiàn)了IHttpHandler。Handler的ProcessRequest方法(或BeginProcessRequest)就是我們要執(zhí)行方法的地方。定義如下:
非異步狀態(tài)的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) { //執(zhí)行方法(下面詳細介紹) 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) { } }
異步狀態(tài)的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) { //執(zhí)行方法(下面詳細介紹) 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) { } }
AjaxHandlerFactory實現(xiàn)了IHandlerFactory接口,用來根據(jù)請求生成具體的Handler,它需要在web.config進行注冊使用。AjaxHandlerFactory的GetHandler是我們攔截請求的第一步。通過請求頭的AjaxFlag:XHR來判斷是否需要我們處理,如果是,則創(chuàng)建一個Handler,否則按照普通的方式進行。由于我們的方法是寫在.aspx.cs內(nèi)的,我們的請求是.aspx后綴的,也就是頁面(Page,實現(xiàn)了IHttpHandler)類型,Page是通過PageHandlerFactory創(chuàng)建的,PageHandlerFactory也實現(xiàn)了IHandlerFactory接口,表示它是用來創(chuàng)建處理程序的。所以我們需要用PageHandlerFactory來創(chuàng)建一個IHttpHandler,不過PageHandlerFactory的構(gòu)造函數(shù)是protected internal類型的,我們無法直接new一個,所以需要通過一個CommonPageHandlerFactory繼承它來實現(xiàn)。
通過PageHandlerFactory獲得Page后,結(jié)合方法名稱,我們就可以反射獲取AjaxMethodAttribute標(biāo)記屬性了。然后根據(jù)它的相關(guān)屬性生成具體的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) { //檢查函數(shù)標(biāo)記 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">當(dāng)前請求</param> private IHttpHandler GetHandler(Page page, string methodName, HttpContext context) { //根據(jù)Page和MethodName進行反射,獲取標(biāo)記屬性(下面詳細介紹) 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); } }
上面的CacheMethodInfo是用于緩存調(diào)用方法的相關(guān)信息的,第一篇我們有提到過優(yōu)化緩存的一些方法,其中就包括緩存+委托。但這里我們并不直接緩存方法的MethodInfo,因為緩存MethodInfo的話,需要通過Invoke去執(zhí)行,這樣的效率比較低。這里我緩存的是方法的委托,該委托的簽名為:Func<object, object[], object>,該委托的返回值為object類型,表示可以返回任意的類型(我們可以在組件內(nèi)部進行處理,例如如果是引用類型(非string),就將其序列化為json,但這里并沒有實現(xiàn))。該委托接收兩個參數(shù),第一個參數(shù)是方法所屬的對象,如果是靜態(tài)方法就是null;第二個參數(shù)是方法的參數(shù),定義為object[]表示可以接收任意類型的參數(shù)。通過委托執(zhí)行方法,與直接調(diào)用方法的效率差別就不是很大(對委托不熟悉的朋友可以參見:委托)。CacheMethodInfo的定義如下:
/// <summary> /// 緩存方法信息 /// </summary> sealed class CacheMethodInfo { /// <summary> /// 方法名稱 /// </summary> public string MethodName { get; set; } /// <summary> /// 方法委托 /// </summary> public Func<object, object[], object> Func { get; set; } /// <summary> /// 方法參數(shù) /// </summary> public ParameterInfo[] Parameters { get; set; } /// <summary> /// Ajax標(biāo)記屬性 /// </summary> public AjaxMethodAttribute AjaxMethodAttribute { get; set; } }
核心方法
1. Eexcutor.GetDelegateInfo 獲取方法相關(guān)信息
該方法用于遍歷頁面類,獲取所有AjaxMethodAttribute標(biāo)記的方法信息,生成一個CacheMethodInfo對象,包括標(biāo)記信息、方法名稱、參數(shù)信息,以及最重要的方法委托。該對象會緩存在一個哈希表中,下次獲取時,直接從內(nèi)存獲得。
/// <summary> /// 獲取頁面標(biāo)記方法信息 /// </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 { //方法標(biāo)記屬性 AjaxMethodAttribute = ca as AjaxMethodAttribute, //方法名稱 MethodName = m.Name, //方法參數(shù)信息 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)) { //根據(jù)MethodInfo獲取方法委托 cacheMethodInfo.Func = ReflectionUtil.GetMethodDelegate(methodInfo); dic.Add(name, cacheMethodInfo); } } ajaxDelegateTable[type.AssemblyQualifiedName] = dic; } CacheMethodInfo currentMethodInfo = null; dic.TryGetValue(methodName, out currentMethodInfo); return currentMethodInfo; }
獲取方法的委托的是通過一個ReflectionUtil獲得的,該類主要用來優(yōu)化反射,它通過Expression,可以將MethodInfo編譯成Func<object,object[],object>委托,為Type編譯一個Func<object>委托,用于創(chuàng)建實例對象。
通過Expression優(yōu)化反射
Expression(表達式樹)允許我們將代碼邏輯以表達式的形式存儲在樹狀結(jié)構(gòu)里,然后在運行時去動態(tài)解析,實現(xiàn)動態(tài)編輯和執(zhí)行代碼。熟悉ORM框架的朋友對Expression肯定很熟悉,因為大部分方法都有一個Expression<TDelegate>類型的參數(shù)。訪問關(guān)系型數(shù)據(jù)庫的本質(zhì)還是sql語句,orm的工作就是為開發(fā)人員屏蔽這個過程,以面向?qū)ο蟮姆绞饺プx寫數(shù)據(jù)庫,而不是自己編寫sql語句。例如,Users.Where(u => u.Age > 18) 就可查詢年齡大于18的用戶。這里不對應(yīng)用在orm的過程進行詳解,下面我們介紹如何用Expression并利用它來生成委托。
.net定義了許多表達式類型,這些類型都派生自Expression,Expression是一個抽象類,而且是一個工廠類,所有類型的表達式都通過它來創(chuàng)建。如圖:
先看一個 1 * 2 + 2 例子,我們用表達樹來描述來描述它:
/* * a * b + 2 */ /* 直接操作 int a = 1, b = 2; int result = a * 2 + 2; */ /* 通過委托調(diào)用 Func<int, int, int> func = new Func<int, int, int>((a, b) => { return a * b + 2; }); func(1, 2); */ /*通過Expression調(diào)用*/ //定義兩個參數(shù) ParameterExpression pe1 = Expression.Parameter(typeof(int), "a"); ParameterExpression pe2 = Expression.Parameter(typeof(int), "b"); //定義一個常量 ConstantExpression constExpression = Expression.Constant(2); //參數(shù)數(shù)組 ParameterExpression[] parametersExpression = new ParameterExpression[]{pe1,pe2}; //一個乘法運算 BinaryExpression multiplyExpression = Expression.Multiply(pe1, pe2); //一個加法運算 BinaryExpression unaryExpression = Expression.Add(multiplyExpression, constExpression); //將上面的表達式轉(zhuǎn)換為一個委托表達式 LambdaExpression lambdaExpression = Expression.Lambda<Func<int, int, int>>(unaryExpression, parametersExpression); //將委托編譯成可執(zhí)行代碼 Func<int,int,int> func = lambdaExpression.Compile() as Func<int,int,int>; Console.WriteLine(func(1, 2));
可以看到我們最終將其編譯為一個具體類型的委托了。下面看我們真正用到的方法是如何實現(xiàn)的,代碼如下:
public static Func<object, object[], object> GetMethodDelegate(MethodInfo methodInfo) { if (methodInfo == null) { throw new ArgumentNullException("methodInfo"); } //定義參數(shù)表達式,它表示委托的第一個參數(shù) ParameterExpression instanceExp = Expression.Parameter(typeof(object), "instance"); //定義參數(shù)表達式,它表示委托的第二個參數(shù) ParameterExpression paramExp = Expression.Parameter(typeof(object[]), "parameters"); //獲取方法的參數(shù)信息數(shù)組 ParameterInfo[] paramInfos = methodInfo.GetParameters(); //參數(shù)表達式集合 List<Expression> paramExpList = new List<Expression>(); int length = paramInfos.Length; for (int i = 0; i < length; i++) { //獲取paramExp參數(shù)數(shù)組的第i個元素 BinaryExpression valueObj = Expression.ArrayIndex(paramExp, Expression.Constant(i)); //將其轉(zhuǎn)換為與參數(shù)類型一致的類型 UnaryExpression valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType); //添加到參數(shù)集合 paramExpList.Add(valueCast); } //方法所屬的實例的表達式,如果為靜態(tài)則為null UnaryExpression instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceExp, methodInfo.ReflectedType); //表示調(diào)用方法的表達式 MethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, paramExpList); //將表達式目錄描述的lambda編譯為可執(zhí)行代碼(委托) 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(); } }
具體代碼都有注釋解釋,最終我們獲得了一個Func<object,object[],object>類型的委托,它會作為CacheMethodInfo的屬性進行緩存。有興趣測試反射性能的朋友,也不妨去測試對比一下這幾種方式執(zhí)行的效率差別:1.直接執(zhí)行方法 2.Emit 3. 緩存+委托 4.Delegate.DynamicInvoke。
2. Executor.Execute 執(zhí)行委托
在執(zhí)行委托前,我們需要先從請求獲取參數(shù),映射到方法。參數(shù)可以是簡單的類型,如 string Test(int i,int j); 也可以是一個對象,如 string Test(User user); 如果是 string Test(User user1, User user2) 也行,提交參數(shù)時只需要加上 user1或 user2 前綴即可,例如 user1.Name,user2.Name。這里沒有支持更多的匹配方式,像mvc,它還支持嵌套類型等等,這些可以自己去實現(xiàn)。如果參數(shù)是一個對象,我們可能需要為它的字段進行賦值,也可能為它的屬性進行賦值。這里我們定義一個DataMember,用來表示字段或?qū)傩缘母割?。如?/p>
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; } } }
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; } } }
定義一個DataMemberManager,用來遍歷Type,獲取所有字段和屬性的,實現(xiàn)如下:
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; } }
在前面我們定義的Handler的ProcessRequest方法中,我們調(diào)用了Executor.Execute,該方法用于執(zhí)行委托,實現(xiàn)如下:
/// <summary> /// 核心函數(shù),執(zhí)行Handler的方法 /// </summary> /// <param name="page">頁面對象</param> /// <param name="context">請求上下文</param> /// <param name="cacheMethodInfo">緩存方法原數(shù)據(jù)</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, "調(diào)用方法匹配到無效的參數(shù)!"); } catch (InvalidCastException) { EndCurrentRequest(context, "參數(shù)轉(zhuǎn)換出錯!"); } catch (System.Threading.ThreadAbortException) { //do nothing } catch (Exception ex) { EndCurrentRequest(context, ex.Message); } }
CacheMethodInfo我們已經(jīng)獲得了,現(xiàn)在只要獲得參數(shù)我們就可以執(zhí)行方法。
GetParameterFromRequest用于從請求獲取object[]參數(shù)數(shù)組。根據(jù)上面所說的,如果參數(shù)是一個簡單類型,那么直接進行轉(zhuǎn)換;如果是實例對象,那么我們先要創(chuàng)建new一個實例對象,然后為其字段或?qū)傩再x值。實現(xiàn)如下:
/// <summary> /// 從請求獲取參參數(shù) /// </summary> /// <param name="request">HttpRequest</param> ///<param name="parameters">參數(shù)信息</param> /// <returns>參數(shù)數(shù)組</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 + " 參數(shù)不被支持"); } realParameters[i] = value; } return realParameters; }
ModelUtil會從Http Request獲取參數(shù),并進行類型轉(zhuǎn)換處理:
internal static class ModelUtil { /// <summary> /// 緩存構(gòu)造函數(shù) /// </summary> private static Hashtable constructorTable = Hashtable.Synchronized(new Hashtable()); /// <summary> /// 根據(jù)名稱從HttpRequest獲取值 /// </summary> /// <param name="request">HttpRequest</param> /// <param name="name">鍵名稱</param> /// <param name="type">參數(shù)類型</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> /// 創(chuàng)建實例對象 /// </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">參數(shù)類型</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); } } }
如果是引用類型,需要通過構(gòu)造函數(shù)創(chuàng)建對象,像前面用于,這里我們也用Expression來構(gòu)建一個Func<object>類型的委托來優(yōu)化,它調(diào)用了ReflectionUtil.GetConstructorDelegate方法。實現(xiàn)如下:
/// <summary> /// 獲取構(gòu)造函數(shù)委托 /// </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構(gòu)造函數(shù)!"); } NewExpression newExp = Expression.New(type); Expression<Func<object>> lambda = Expression.Lambda<Func<object>>(newExp); return lambda.Compile(); }
最后再輸出結(jié)果時,如果是Get請求,并且需要緩存,我們還需要設(shè)置一下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(); }
總結(jié)
現(xiàn)在不管我們前臺用什么腳本庫,只要按照約定就可以調(diào)用標(biāo)記方法。上面已經(jīng)介紹了組件的核心部分,您也可以按照自己的想法進行擴展,也歡迎共同學(xué)習(xí)交流。
相關(guān)文章
使用ajax技術(shù)無刷新動態(tài)調(diào)用新浪股票實時數(shù)據(jù)
由于最近網(wǎng)速慢的緣故,查看股票信息時網(wǎng)頁老是打不開。這幾天一直在研究ajax,于是用jquery自己做了一個自動讀取新浪股票實時數(shù)據(jù)的頁面2014-08-08登錄超時給出提示跳到登錄頁面(ajax、導(dǎo)入、導(dǎo)出)
這篇文章主要介紹了登錄超時給出提示跳到登錄頁面(ajax、導(dǎo)入、導(dǎo)出)的相關(guān)資料,需要的朋友可以參考下2016-02-02將xml文件作為一個小的數(shù)據(jù)庫,進行學(xué)生的增刪改查的簡單實例
下面小編就為大家?guī)硪黄獙ml文件作為一個小的數(shù)據(jù)庫,進行學(xué)生的增刪改查的簡單實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-06-06Ajax修改數(shù)據(jù)即時顯示篇實現(xiàn)代碼
上一篇我們講了如何使用ajax向數(shù)據(jù)庫添加數(shù)據(jù),今天我們要大家學(xué)習(xí)的課程是:使用ajax修改數(shù)據(jù)庫數(shù)據(jù),并在客戶網(wǎng)頁立即顯示新的內(nèi)容.當(dāng)然在修改的過程中同樣不會有刷新網(wǎng)頁的情況發(fā)生!2010-10-10ajax 動態(tài)傳遞jsp等頁面使用id辨識傳遞對象
本文為大家介紹下使用ajax動態(tài)傳遞jsp等頁面,js的jax編寫,使用id辨識傳遞對象2014-01-01ajax提交session超時跳轉(zhuǎn)頁面使用全局的方法來處理
如果session超時,而且是ajax請求,就會在響應(yīng)頭里。再用一個全局的方法來處理,session超時要跳轉(zhuǎn)的頁面2013-11-11