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%" };
}
 
//以下为系统公开的可调用方法
View Code

然后就是后台代码,我已经加入了主要的注释,各位看官请看好.

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;
        }
    }
}
View Code

总之,操作过程就是,有请求发来,就动态在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.");
        }
View Code

前台调用部分:

 <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>
View Code

看上去是不是简洁了许多?

需要说明的是,我们还有很多方式来让前台调用后台,除了这种利用反射来动态生成中间JS文件以外,我们还可以通过在服务端维护一个Dictionary来进行,Dictionay的key存储函数签名,value存储函数体,这也是一种不错的设计方式.

效果图展示

参考:

非常感谢这篇博客,一个基于jQuery ajax和.net httphandler 的超轻异步框架,千行代码完成。

源码下载

点击这里下载

posted on 2013-05-16 17:00  程序诗人  阅读(3499)  评论(24编辑  收藏  举报