asp.net異步的不當使用方式
在進行asp.net ajax設計的時候,我們肯定避免不了利用JQuery的ajax函數取調用HttpHandler中的數據.在我開始學習的時候,我總是這么用的,那時候頭腦中沒有什么概念,只知道有了新需求就新增ashx文件,復制粘貼原有的ajax請求代碼,稍微修改一下即可. 所以文件中總是充斥着大量的可粘貼復制的代碼:
$.ajax({ type : "post", contentType: "application/json", datatype : "json", url : "WebHandler.ashx/GetUserList", data : "{UID:1}", success : function(data) { alert("ok") } });
這樣做的好處就是自己很開心,也費不了多少時間就可以將功能修改好.但是帶來的負面效果將是致命的.
(1) 大量的代碼結構基本上一致,粘貼復制不但容易出錯,而且項目一大,修改起來就比較麻煩.倘若一個地方出錯, 好多地方修改.
(2) 代碼中的URL請求非常不保險,一旦項目結構發生變化,路徑發生變化,項目的修改就需要跟進,項目一大,這種修改是致命的.
(3) 這種工作不能很好的協調前端開發和后端開發, 由於前端直接通過ajax請求后端數據,導致二者的配合需要非常緊密(前端設計有時候需要看后端代碼去確定需要調用哪個方法).不好獨立的進行開發.
(4) 就是有違軟件設計軟則,大量重復代碼充斥,后期維護困難,牽一發而動全身.
比較好的解決方式
所以基於以上幾點,決定針對這四點着重解決.
(1) 大量重復代碼可以寫到一個公共的js文件中,然后將請求文件參數當作參數傳出:
$.ajaxRequest("WebHandler.ashx/GetUserList ", "{}", function(result) {......});
這樣使用的時候,直接添加好js文件,然后調用即可.只關注邏輯,代碼量顯著下降.
(2) 當項目文件改變的時候,上面的方法顯然無法解決問題,所以最好能夠將url自動檢測. 所以我期望的是這樣調用:
$.ajaxRequest.GetUserList (“{}”, function (result {……}) ;
這樣就解決了項目變動,不能找到handler文件的問題.
(3) 這個問題,可以通過新增一個中間文件來解決.最好的辦法就是客戶端請求時,后台能夠動態生成供前台調用的js代碼.
(4) 這個主要是代碼編寫這塊. 與設計方面暫無關系.
所以,總結一下,就是由原先的前台通過ajax直接發送請求到后台,然后后台返回數據的過程, 修改成了前台通過ajax發出請求,后台動態生成js中間文件,前台然后調用即可.
所以這里我總結一下我們的需求,
就是,前台發送$.ajaxRequest.GetUserList (“{}”, function (result {……}) ;出去,后台接收之后,動態創建js文件,然后供前台調用.
那么這里我們需要的東西就是,一個js模板,能夠承載動態生成的函數, 一個ashx文件,能夠接收請求,並且讀取模板並修改之.
代碼閱讀
模板代碼我就不寫了,直接找現成的, 感謝作者的開源:

/*---------------------------------------------------------------------------- --功能說明: %H_DESC% --創建時間: %H_DATE% --其它信息: 此文件自動生成,並依賴json2.js <http://www.JSON.org/json2.js> --內核維護: wzcheng@iflytek.com ------------------------------------------------------------------------------*/ (function($) { if (!$.net) { var defaultOptions = { contentType: "application/json; charset=utf-8" , dataType: "json" , type: "POST" //, complete: function(r, status) { debugger; } //此代碼加上用於全局調試 }; //將net作為命名空間擴展到jQuery框架內 $.extend({ net: {} }); //將調用WEB SERVICES的代理函數CallWebMethod擴展到jQuery.svr框架內 $.extend($.net, { CallWebMethod: function(options, method, args, callback) { //調用第三方對象序列化成JSON字符串的方法 var jsonStr = JSON.stringify(args); var parameters = $.extend({}, defaultOptions); var url0 = options.url + "/" + method; $.extend(parameters, options, { url: url0, data: jsonStr, success: callback }); $.ajax(parameters); } }); } //將指定類型的WEB服務擴展到jQuery框架內 var services = new %CLS%(); $.extend($.net, { %CLS%: services }); })(jQuery); /*---------------------------------------------------------------------------- --功能說明: 服務的構造函數 ----------------------------------------------------------------------------*/ function %CLS%() { /* --定義本地的調用選項,如果希望改變個別的ajax調用選項, --請在對象中添加其它選項的鍵/值 */ this.Options = { url: "%URL%" }; } //以下為系統公開的可調用方法
然后就是后台代碼,我已經加入了主要的注釋,各位看官請看好.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; using System.Web.SessionState; using System.Reflection; using System.IO; using System.Web.Script.Serialization; using System.Collections.Specialized; using System.Web.Script.Services; namespace EDaemonCore { public class CoreHandler : IHttpHandler, IRequiresSessionState { private HttpContext context; public bool IsReusable { get { return true; } } /// <summary> /// Request請求入口 /// </summary> public void ProcessRequest(HttpContext context) { this.context = context; //獲取函數簽名並將其觸發 string inputMethod = GetInputMethod(); object result = Invoke(inputMethod,GetParameterValue()); //生成JS模板 StringBuilder sbStr = GenerateJsTemplate(); //將結果打印出去 context.Response.Write(result); } /// <summary> /// 得到JS模板,並將其中的關鍵字做替換,能夠解決目錄或者是名稱變更,找不到handler文件的問題. /// </summary> public StringBuilder GenerateJsTemplate() { Type type = this.GetType(); Uri url = HttpContext.Current.Request.Url; string script = GetJsTemplate(); script = script.Replace("%H_DESC%", "通過jQuery.ajax完成服務端函數調用"); script = script.Replace("%H_DATE%", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); script = script.Replace("%URL%", url.Query.Length > 0 ? url.ToString().Replace(url.Query, "") : url.ToString()); script = script.Replace("%CLS%", type.Name); StringBuilder scriptBuilder = new StringBuilder(script); MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); foreach (MethodInfo m in methods) { //ResponseAnnotationAttribute resAnn = this.GetAnnation(m.Name); //scriptBuilder.AppendLine("/*----------------------------------------------------------------------------"); //scriptBuilder.AppendLine("功能說明:" + resAnn.Desc); //scriptBuilder.AppendLine("附加說明:緩存時間 " + resAnn.CacheDuration.ToString() + " 秒"); //scriptBuilder.AppendLine(" 輸出類型 " + resAnn.ResponseFormat.ToString()); //scriptBuilder.AppendLine("----------------------------------------------------------------------------*/"); string func = GetFunctionTemplete(m); scriptBuilder.AppendLine(func); } return scriptBuilder; } /// <summary> /// 將后台業務代碼動態添加到JS文件中,供前台調用 /// </summary> private static string GetFunctionTemplete(MethodInfo method) { StringBuilder func = new StringBuilder(method.DeclaringType.Name); func.Append(".prototype." + method.Name); func.Append("=function"); func.Append("("); foreach (ParameterInfo p in method.GetParameters()) { func.Append(p.Name + ","); } func.Append("callback)"); func.AppendLine("{"); { func.Append("\tvar args = {"); foreach (ParameterInfo p in method.GetParameters()) { func.Append(p.Name + ":" + p.Name + ","); } func.AppendLine("ajax:'jquery1.4.2'};"); //switch (format) //{ // case ResponseFormat.Xml: // func.AppendLine("\tvar options={dataType:'xml'};"); // break; // case ResponseFormat.Json: // func.AppendLine("\tvar options={dataType:'json'};"); // break; // default: // func.AppendLine("\tvar options={dataType:'text'};"); // break; //} func.AppendLine("\tvar options={dataType:'text'};"); func.AppendLine("\t$.extend(true,options,{},this.Options);"); func.AppendFormat("\t$.net.CallWebMethod(options,'{0}', args, callback);", method.Name); func.AppendLine(); } func.AppendLine("}\t\t"); return func.ToString(); } /// <summary> /// 文件流操作,讀取JS模板文件 /// </summary> private string GetJsTemplate() { Type type = typeof(CoreHandler); AssemblyName asmName = new AssemblyName(type.Assembly.FullName); Stream stream = type.Assembly.GetManifestResourceStream(asmName.Name + ".ScriptDaemon.net.js"); if (stream != null) { byte[] buffer = new byte[stream.Length]; int len = stream.Read(buffer, 0, (int)stream.Length); string temp = Encoding.UTF8.GetString(buffer, 0, len); return temp; } else { throw new Exception("模版未找到"); } } /// <summary> /// 獲取當前請求的信息,如果有請求函數,則轉至請求函數,如果沒有,則代表需要生成動態JS文件 /// </summary> private string GetInputMethod() { string[] segmentCollection = this.context.Request.Url.Segments; int segmentLength = segmentCollection.Length; string inputMethod = segmentCollection[segmentLength - 1]; if (inputMethod.LastIndexOf(".ashx") >= 0) inputMethod = "GenerateJsTemplate"; return inputMethod; } /// <summary> /// 動態調用有參/無參methodName方法並返回結果 /// </summary> /// <param name="methodName">函數簽名</param> /// <param name="args">參數內容</param> /// <returns>返回內容</returns> private object Invoke(string methodName, Dictionary<string, object> args) { MethodInfo specificMethod = this.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public); if (specificMethod == null) throw new Exception("The method is not exist, pls check."); List<object> argsList = new List<object>(); ParameterInfo[] parameterInfo = specificMethod.GetParameters(); // Get the parameters foreach (ParameterInfo p in parameterInfo) //Loop { if (args.ContainsKey(p.Name)) { object obj = args[p.Name]; // get parameter value argsList.Add(Convert.ChangeType(obj, p.ParameterType)); } } object[] parameters = argsList.ToArray(); object result = specificMethod.Invoke(this, parameters); return result; } /// <summary> /// 動態獲取參數並保存 /// </summary> private Dictionary<string, object> GetParameterValue() { Stream inputStream = this.context.Request.InputStream; inputStream.Position = 0; //reset the position to 0 byte[] buffer = new byte[inputStream.Length]; inputStream.Read(buffer, 0, buffer.Length); //read stream data into buffer Encoding inputEncoding = this.context.Request.ContentEncoding; string inputStr = inputEncoding.GetString(buffer); JavaScriptSerializer jsSerializer = new JavaScriptSerializer(); object obj = jsSerializer.DeserializeObject(inputStr); Dictionary<string,object> paramDict = obj as Dictionary<string, object>; NameValueCollection queryStr = this.context.Request.QueryString; foreach (string name in queryStr) { paramDict.Add(name,queryStr[name]); } return paramDict; } } }
總之,操作過程就是,有請求發來,就動態在JS中生成與服務端一致的簽名函數,然后客戶端調用.
這里,我們可以添加點函數來測試:
后台:

public string GetTestMessage(int flag,string content) { return string.Format("User {0} says: this is {1} message.", flag, content); } public string GetMessageTest() { return string.Format("content:haha this is a ttttest."); }
前台調用部分:

<script src="WebHandler.ashx" type="text/javascript"></script> <script type="text/javascript"> $(document).ready(function(){ $("#Button1").bind("click",function(){ $.net.WebHandler.GetMessageTest(function(data){ alert(data); }); }); $("#Button2").bind("click",function(){ $.net.WebHandler.GetTestMessage(731,'ShiChaoYang',function(data){ alert(data); }); }); }); </script>
看上去是不是簡潔了許多?
需要說明的是,我們還有很多方式來讓前台調用后台,除了這種利用反射來動態生成中間JS文件以外,我們還可以通過在服務端維護一個Dictionary來進行,Dictionay的key存儲函數簽名,value存儲函數體,這也是一種不錯的設計方式.
效果圖展示
參考:
非常感謝這篇博客,一個基於jQuery ajax和.net httphandler 的超輕異步框架,千行代碼完成。
源碼下載