小小目錄:
現在Ajax在web應用方面已經用的稀巴爛了,如果你做一個網站沒有用到ajax都不好意思拿出手,雖然面對ajax的潮流下,在.net開放方向,微軟已經做了很多工作了,比如推出了ajax的服務器端控件,但是他不夠靈活,用的人多嗎?不多!
在.Net環境下與ajax配合的幾種情況(主要是針對webform,因為.net mvc你只要寫action就可以將方法給發布出來,實現ajax的調用):
1.用ajax訪問aspx頁面:直接在page_load中輸出html代碼並且End掉多余標簽的輸出,但是他還要走生命周期,一個好好的aspx頁面只用這個功能怪怪的,當然還可以直接在aspx.cs上面的方法上加上[webMethod]特性來標記ajax訪問,但是必須該方法為靜態,用起來也不是很爽。
2.用ajax訪問ashx一般處理程序:這個是在.net的ajax開發中一種常見明確推薦的方式,他輸出頁面少,不冗余代碼,調用靈活,但是他通常一個ashx頁面只提供一個功能,但是市面上有可以通過switch方法的參數或者用委托或者反射的方法來實現一個ashx頁面提供多個ajax訪問的功能,但是沒發現這樣也挺亂的嗎,我之前一直用這個,慢慢的 慢慢的開始討厭這種寫好寫法!
3.用ajax直接調用webservice服務:這樣的好處是在一個普通的webserive類中可以使用多個對外ajax訪問點的發布,用起來很靈活,但是你一般的小網站為了ajax的訪問還是部署webservice還是比較煩的吧!
4.用ajax去調用wcf:微軟推出了wcf,並且現在公認為他是一種可以替代webservice的新產品,並且他還支持獎wcf部署在web服務器上,以url形式發布,當然了,這樣就可以用ajax來訪問調用,但是同上,小網站小應用還要去弄wcf,也是挺煩的一件事情!
不知道大家發現沒有,第一二中方法其實終於還是訪問了業務類里面的方法,是否感覺ajax調ashx,aspx有些多余?還有第三四中方法其實可以看做業務類里面的方法,但是需要發布才可以使用,挺煩!那有沒有可能讓ajax直接去訪問cs類里面的方法呢?
我就開始查資料,看看網上別人的一些做法和看法,之前在.net方面有個叫做ajaxpro的dll可以實現cs發布成ajax,但是感覺使用起來還是有點復雜,就自己琢磨着自己能否開發一個.net的ajax后台框架,在普通cs類的方法上面稍加修飾,不改變方法原有功能,但是能將方法發布出來?
經過幾點的奮戰,終於有了一個初步可以用的版本,先貼上設計的類圖:
看了這個圖是不是看上出還是比較繁瑣?那接下來再來看下這個框架運行的泳道圖應該就比較明了:
他其實就分讓相應的url進入指定的handlerfactory里面,這個factory去指定調用相應的handler(你可以看做ashx文件),然后去解析地址欄的url,得到具體的方法,再去動態執行,最后輸出結果即可,大致流程就是這樣,現在我來說一些細節:
url的一些解析問題:本次設計的url例如:http://domain.com/classname/method.ajax?param...,從url里面可以解析得到此次請求的class類名以及具體的method方法名,當然所屬的程序集是需要配置的好的,這樣就可以通過反射具體得到方法
如何得到方法:由於得到的類名,方法名都是字符串,我們知道可以使用.Net里面的反射功能來實現通過字符串去調用相應的方法,但是反射消耗的性能還是很大的,所以我做了緩存,在一個方法成功請求之后我就將該方法緩存起來,以便下一次的時候不需要再反射直接調用緩存.

1 #region 獲取該方法 2 3 if (_idictMethod.Keys.Contains(this.DictKey)) 4 { 5 #region 存在緩存中 直接從緩存中取得 6 //如果該方法被訪問過 存在該字典中 7 customMethodInfo = _idictMethod[this.DictKey]; 8 //更新執行時間 9 customMethodInfo.LastUpdateTime = DateTime.Now; 10 //訪問次數加1 11 customMethodInfo.Count++; 12 //將調用后的信息反寫進去 13 _idictMethod[this.DictKey] = customMethodInfo; 14 #endregion 15 } 16 else { 17 #region 如果緩存中不存在 將會反射重新獲取方法信息 並且記錄緩存 18 customMethodInfo = this.GetMethodBaseInfo(); 19 if (customMethodInfo == null) 20 { 21 throw new MethodNotFoundOrInvalidException(string.Format("沒有找到方法{0}", this._methodPathInfo.MethodName)); 22 } 23 else 24 { 25 #region 初始化方法的一些信息 26 //特性列表 27 customMethodInfo.AttrList = ReflectionHelper.GetAttributes<ValidateAttr>(customMethodInfo.Method); 28 //參數列表 29 customMethodInfo.ParamterInfos = customMethodInfo.Method.GetParameters(); 30 //返回值類型 31 32 customMethodInfo.RetureType = customMethodInfo.Method.ReturnType; 33 //方法最后的更新時間 34 customMethodInfo.LastUpdateTime = DateTime.Now; 35 //方法的執行次數 36 customMethodInfo.Count = 1; 37 #endregion 38 39 #region 加了雙重鎖 防止死鎖掉 將該方法加入緩存 40 //通過了特性的檢測 41 if (!_idictMethod.Keys.Contains(this.DictKey)) 42 { 43 lock (obj) 44 { 45 //防止在鎖的時候 其他用戶已經添加了鍵值 46 if (!_idictMethod.Keys.Contains(this.DictKey)) 47 { 48 //將 此方法的信息記錄到靜態字典中 以便下次從內存中調用 49 _idictMethod.Add(this.DictKey, customMethodInfo); 50 } 51 } 52 } 53 #endregion 54 55 } 56 #endregion 57 } 58 #endregion
並且為了盡量少反射,減少用戶請求時間,在第一次程序運行的時候,我就將可能為標記ajax的方法給預緩存了起來(用靜態構造方法可以實現該功能,該構造方法僅僅在第一次使用該類的時候運行,並且只運行一次)

1 #region 初始化緩存 2 /// <summary> 3 /// 初始化緩存 4 /// </summary> 5 private static void InitCache() 6 { 7 ICollection assemblies = BuildManager.GetReferencedAssemblies(); 8 9 foreach (Assembly assembly in assemblies) 10 { 11 12 if (UrlConfig.ASSEMBLY.Equals(assembly)) 13 { 14 //如果在指定的ajax的業務的程序集中 15 16 //添加到程序集的緩存中 17 if (!_idictAssemby.Keys.Contains(GetAssemblyName(assembly))) 18 { 19 _idictAssemby.Add(GetAssemblyName(assembly), assembly); 20 } 21 22 try 23 { 24 foreach (Type t in assembly.GetExportedTypes()) 25 { 26 Type[] allInterface = t.GetInterfaces(); 27 foreach (Type interfaceName in allInterface) 28 { 29 if ("IAjax".Equals(interfaceName.Name)) 30 { 31 // 該類有IAjax的接口 則默認添加進緩存 添加到類的緩存中 32 if (!_idictClass.Keys.Contains(t.FullName)) 33 { 34 _idictClass.Add(t.FullName, t); 35 } 36 37 } 38 } 39 } 40 } 41 catch { } 42 } 43 44 45 } 46 47 //針對方法添加緩存 48 foreach (string className in _idictClass.Keys) 49 { 50 foreach (MethodInfo methodInfo in _idictClass[className].GetMethods(_bindingAttr)) 51 { 52 try 53 { 54 List<ValidateAttr> attrList = ReflectionHelper.GetAttributes<ValidateAttr>(methodInfo); 55 56 //有標志的WebMethodAttr屬性 添加進方法的緩存 57 WebMethodAttr webMethodAttr = attrList.Find(x => x is WebMethodAttr) as WebMethodAttr; 58 if (webMethodAttr == null) 59 { 60 //沒有該特性 61 continue; 62 } 63 CustomMethodInfo customMethonfInfo = new CustomMethodInfo() 64 { 65 AttrList = attrList, 66 Count = 0, 67 LastUpdateTime = DateTime.Now, 68 Method = methodInfo, 69 RetureType = methodInfo.ReturnType, 70 ParamterInfos = methodInfo.GetParameters(), 71 Instance = Activator.CreateInstance(_idictClass[className]), 72 Assembly = _idictClass[className].Assembly 73 74 }; 75 //添加進方法的緩存里面去 76 if (!_idictMethod.Keys.Contains(className+"."+methodInfo.Name)) 77 { 78 _idictMethod.Add(className + "." + methodInfo.Name, customMethonfInfo); 79 } 80 81 }catch{} 82 83 84 } 85 } 86 } 87 88 /// <summary> 89 /// 得到程序集的名稱 不帶版本信息的 90 /// </summary> 91 /// <param name="assembly"></param> 92 /// <returns></returns> 93 private static string GetAssemblyName(Assembly assembly) 94 { 95 return assembly.FullName.Split(',')[0]; 96 } 97 #endregion
這樣就可以盡可能得降低反射因為消耗的性能而帶來產生的效率問題!
本框架在方法特性上作了擴展,以標記特性的方式可以對ajax方法做一些操作,比如參數的驗證,緩存的輸出等,並且該特性都是很容易擴展的,只要繼承ValidateAttr類即可實現接口的擴展,為以后比如用戶權限認證,訪問量限制等需要提供很好的解決方案

1 #region 檢查方法的特性 (未完善) 2 /// <summary> 3 /// 檢查方法的特性 (未完善) 4 /// 現在主要是請求權限的驗證 參數的驗證 5 /// </summary> 6 /// <param name="methodInfo"></param> 7 /// <param name="errMsg">驗證失敗時帶出的消息</param> 8 /// <returns></returns> 9 private bool CheckAttribute(List<ValidateAttr> attrList) 10 { 11 bool ret = true; 12 if (attrList.Count > 0) 13 { 14 foreach (ValidateAttr attr in attrList) 15 { 16 attr.CurrentHttpRequest = this._httpRequestDescription; 17 #region 判斷此特性是否能通過驗證 18 if (!attr.IsValidate()) 19 { 20 //特性的驗證規則失敗 21 ret = false; 22 break; 23 } 24 #endregion 25 } 26 } 27 else 28 { 29 throw new MethodNotFoundOrInvalidException("此方法並不是網絡方法,無法直接訪問"); 30 } 31 32 return ret; 33 } 34 #endregion
在不可或缺的參數驗證特性上面簡單的使用了策略模式,可以低耦合的實現其他一些參數類型擴展

最后在頁面輸出方面,使用了一個簡單的輸出幫助以,以AjaxResult結果寄存類和json的一個序列化類就行輸出

/// <summary> /// /// </summary> /// <param name="returnValue">返回值</param> /// <param name="returnType">返回類型</param> /// <returns>返回字符串</returns> public static string GetResponseString(object returnValue,Type returnType) { string ret = string.Empty; try { if (returnType.IsSampleType()) { //返回的是簡單類型 AjaxResult ajaxResult = new AjaxResult() { Flag = "1", Data = Convert.ToString(returnValue) }; ret = ajaxResult.ToString(); } else { ret = JsonConvert.SerializeObject(returnValue); } } catch (Exception ex) { //遇到異常 AjaxResult errResult = new AjaxResult() { Flag = "0", ErrorMsg = ex.Message }; ret = errResult.ToString(); } return ret; } /// <summary> /// 返回錯誤 /// </summary> /// <param name="errorMessge"></param> /// <returns></returns> public static string ResponseError(string errorMessge) { //返回錯誤 AjaxResult errResult = new AjaxResult() { Flag = "0", ErrorMsg = errorMessge }; return errResult.ToString(); }
3、框架如何使用
本框架暫時命名為AjaxFramework,所以你在使用本框架時只需要引用該dll,然后在webconfig里面針對不同的IIS版本配置好相應的HttpHandler

<httpHandlers> <!--針對IIS6 再這里配置handler 以支持ajax后綴的擴展--> <add verb="*" path="*.ajax" validate="true" type="AjaxFramework.AjaxHandlerFactory,AjaxFramework" /> </httpHandlers> <handlers> <!--針對IIS7 再這里配置handler 以支持ajax后綴的擴展--> <add name="ajaxhandler" verb="*" preCondition="integratedMode" path="*.ajax" type="AjaxFramework.AjaxHandlerFactory,AjaxFramework" /> </handlers>
這里的.ajax后綴根據你的需求修改,比如你可以修改成.json,以后以相應的后綴訪問就可以了
再配置好你需要公布的命名空間

<appSettings> <!--在此處配置ajax后台框架所需映射的項目--> <add key="AjaxFramewok" value="TestBLL"/> </appSettings>
至此,配置已經完成,是不是還算簡單!
用法就如下了

這標明TestBLL空間里面的Data類里面的Add方法被發布了出來,是不是很簡單,只需要添加相應的特性即可完成發布網絡方法的操作
然后你可以用http://domain.com/data/add.ajax?a=4&b=6.787的url形式就行訪問了
4、 框架使用效果圖
先來看一下上面一個章節里面那個發布方法的效果圖:
我們來看下用Get方式去訪問Post標記方法的情況

/// <summary> /// 這個方法只有Post請求才可以 /// </summary> /// <returns></returns> [WebMethodAttr(RequestType.Post)] public string Get_Pat() { return "pat"; }
我們來看下普通類型返回的情況

/// <summary> /// 返回普通的字符串 會加上一個json的外殼 /// </summary> /// <returns></returns> [WebMethodAttr(RequestType.Get)] [OutputCacheAttr(20)] public string Get_Pat2() { return "pat" + DateTime.Now.ToString("yyyyMMddHHmmssfff"); }
但是如果返回的是DataTable,會怎么樣呢?

/// <summary> /// 返回DataTable的數據 /// </summary> /// <returns></returns> [WebMethodAttr(RequestType.Get)] public DataTable Get_Data() { DataTable dt = new DataTable("dt"); dt.Columns.Add("id"); dt.Columns.Add("name"); DataRow row = dt.NewRow(); row["id"] = 1; row["name"] = "tom"; dt.Rows.Add(row); DataRow row2 = dt.NewRow(); row2["id"] = 2; row2["name"] = "peter"; dt.Rows.Add(row2); return dt; }
那么如果方法的參數是實體類型,我們該如何傳呢?

/// <summary> /// 這個方法是用來測試傳實體的 /// </summary> /// <param name="user"></param> /// <returns></returns> [WebMethodAttr(RequestType.Get)] public User Insert_User(User user) { return user; }
估計方法+配圖,大家應該看的很明白了吧!
在這里提一下,由於現在ajax的請求返回類型基本都是json格式,所以本框架暫時只支持json格式!
5、框架的優缺點
先來說優點吧:
1:你以后在使用ajax的時候不需要再加各種ashx,aspx頁面,加上相應的特性即可將你業務層里面的方法給發布出來
2:利用特性擴展這一特點可以很方便的滿足你其他的要求
3:運用了各種方式的緩存,效率應該不會將到哪里去
3:整體框架還算簡單,運行思路明了,相對wcf,webservice這些復雜的框架,以后出錯可以手動調試源碼
估計缺點也有很多:
1:由於是通過反射來運行方法的,某些時候效率可能會不怎么高
2:在參數動態賦值模塊對於其他的一些參數可能會有問題
3:發布方法無法重載
4:只是剛剛寫出來,沒有投入到實際項目中,可用性還有待商榷
這個框架可以用哪里?
一般的小OA,ERP,個人博客站,小的CMS站,普通企業站個人覺得這個框架還是可以應付的,如果需要在其他類型的網站上使用你就自己看着辦吧!
6、源碼下載
我厚着臉皮的發到了Github上面去,點我去Github下載AjaxFramework(json類庫在lib文件夾中,可能需要重新引用一遍),希望有興趣的同學可以加入進來一些開發哦,有問題留言吧,很期待您的指導!