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> --内核维护: [email protected] ------------------------------------------------------------------------------*/ (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 的超轻异步框架,千行代码完成。
源码下载