一个基于jQuery ajax和.net httphandler 的超轻异步框架,千行代码完成。
我部正在开发的一个新项目,选择了jQuery作为基础的Javascript函数库。同时确定了jQuery提供的ajax系列方法作为异步推拉数据的基础接口。
使用过ajax方法的同事应该是知道的,ajax方法需要提供一组选型,比如content-type、mime-type、data、async等等,这些选项的组合搭配可以构成不同的请求方式,必须理解HTTP的部分协议才能够正确的完成配置。当然,也可以从google上去找配置好的代码片段copy过来用, 关于这种方式的缺陷就不多说了。
1. 没有在代码编写之前,规定好如何利用jQuery的ajax方法。导致开发人员直接把ajax方法的调用写在页面的脚本块内,导致页面内出现了如下所示的代码:
$.ajax({ type: "post", contentType: "application/json",datatype: "json", async: false, url: "DemoHandler.asmx/GetData",data: "{index:1}", complete: function(e, s) { alert("ok")});
上面的代码的意图是通过http的post方法,调用服务端web services的GetData方法,传入的参数名称为index,值为1 。这是以硬编码方式与服务端达成的强耦合关系的编码方式,隐含了如下的问题:
1. DemoHandler.asmx文件的路经改变了,就会影响页面内所有这样的代码;
2. 服务端GetData的方法签名如果变化了,就会影响页面内所有这样的代码;
3. 如果以后出现了更好的ajax框架,替换的工作量也是如同推倒重来的;
4. GetData这样的方法如果被其他地方调用,采用的方法是copy一段这样的代码;
5. 当我们提倡前后台分开的开发的情况下,理论上前台开发人员精通的技术应该是div+css、js这些,后台开发才关注web services这样的技术。只有这样才能把各自的领域做精、最好。上面所示代码的第一个模板是我提供的,因为我个人扮演了联通前台和后台技术的桥梁角色,但反思起来他是个人行为,却不是团队力量的体现。
6. 开发人员可以自问一下,contentType: "application/json" 这个选项起到什么作用?为什么DemoHandler.asmx/GetData这样的写法可以成功调入到web services方法内部?为什么data要写成 "{index:1}" 这样的样式?我尝试问了项目组的人,答案都是不知道。很明显,真正和开发有关的只是业务,而不是这些底层协议方面的知识,它是可以对大家屏蔽的。
当把存在的问题分析清楚了,答案也就有了。我们为每一个web serivices写一个专门的js文件,它内部封装了一组与web method一致签名的函数。这样页面只通过js方法传参数调用即可,而不再关注那些ajax调用选项的细节。达到了相关概念的逻辑统一封装和管理、一点维护多点使用、不同职责的开发人员关注不同功能这些要求。
在这件事情的执行上,又出现了一个小插曲。负责写js文件的同学参考了其它网上牛人的js书写风格,也玩起了风靡一时的js花招—动态对象、闭包。当然很快就被否定了,其它原因不多讲,只有一点原因需要特别说明:任何编程技巧和技术的使用都要考虑它是否适合团队中的一般水平,如果不适合就要考虑其它办法。闭包显然只是一个技巧而已,不是解决问题的王道。它的引入至少给一般人阅读代码增加了难度,所以要被PASS掉。
最终还是选择采用大白话式的prototype来声明服务对象的方法。一开始,我写了一个头部的初始化函数,该函数封装好了所有的选项配置信息,以及把服务对象扩展到jQuery体系中去,使开发人员可以按照一致的规格调用服务端函数。后期,其他开发人员只要在文件的尾部按需追加一个个与服务端方法对应的函数即可。
为了利用.net script services对web services的扩展,项目组一开始规定后台直接提供web services对客户端进行暴露。这对客户端提交数据没有问题,但对我们使用的某些控件就不太适合了。因为控件要求返回值是一个纯粹的json数据,而script services框架会在输出的json数据外层再包上一个名d的根,这就导致控件无法识别这个数据源。于是项目组通过ashx文件,实现Ihttphandler来手动处理请求。
很快,服务端工程里多出了10几个ashx文件,因为客户端需要10几种不同的数据请求。每个ashx内部的ProcessRequest方法处理四件大事:1)设置WEB反馈的HTTP头信息;2)处理业务逻辑;3)序列化业务数据为JSON;4)把JSON数据写到输出流中。
这个现象说明了两个问题:1)ashx文件的数量会随着应用需求的增多而不断的增多,今后非常难以维护;2)四件事情只有一件是必须每次都做的,即处理业务逻辑,其它事件都是有规律并重复的。
基于对上述问题,项目组做出了改进,实现一个统一的WEB请求的调度和处理机制,使得同一类的请求可以放在一个ashx文件内完成,并且每个请求直接映射到一个函数来完成,输出、输出、缓存都由这个机制来处理;另外,普通开发人员只需要完成业务逻辑代码即可,其它一概不要关心。
时间又过了2天,新的问题再此困扰了我。如果服务端增加一个新的方法,客户端的js也要与之匹配的增加一个函数作为调用代理。这样同一语意的函数,就产生了前后台两点维护,这是不合理的。它也是一个机械性的工作,每次都要开发手动来做,这也是不人性的。于是,新的解决方法是当客户端直接请求ashx文件的时候,服务端自动生成相应的客户端代理脚本。这样带来了客户端代码更大的改进,我们只需要将 <script src=”..\xHandler.ashx”>置入页面,一切就OK了。因为自动获得的js脚本会自动初始化客户端服务,后续就可以在页面的任何地方通过例如这样的方法调用服务了: $.net. xHandler.GetData()
这样改进之后,项目组的开发人员是充满喜悦的,因为他们可以少做很多不必要的事情,可以把代码写的更加整洁,可以让BUG变的更少。从此,我们也把整合jQuery.ajax和.net的技术方案固化了下来,以后团队将依赖存在的技术稳定的工作,而不再依赖个人。顺便说一句,在我们写出这些代码的时候,已经考虑过例如ajaxpro.net、.NET MVC这样的框架,只是他并不适合我们而已。
最后,附源码:WebHandler.zip
有朋友问:你为什么不使用ajaxpro呢?我有一个很简单的理由,因为我有足够的实力来驾驭目前的需求。当我发现1000行代码可以搞定需求的情况下,我是不会去引入一套DLL的,何况我也要学习它。完成此文章所提模块,笔者所花费的时间也仅仅是一个下午加一个晚上而已。我坚持一个理念:不要因为需要一点色彩,就把整个染缸都搬过来。
5.27 日有朋友问了一个问题:如果.ashx包含了n个方法,但前端只需要一个或者其中的几个,那么现在的情况客户端都会生成包含所有方法的JS,请问如何解决这个问题?答案有三种:
1. 在脚本引用块script src 属性引用.ashx时,后面可以带上参数,如 src=xHandler.ashx?find=method&like=get*, 同步修改 GetSvr方法,使其签名为 public StringBuilder GetSvr(string find,sting like) 这样就可以获得客户端传来的参数,其中find表示按什么方式匹配方法,如按名称匹配,like表示匹配的模式,如所有get开头的方法,接下来修改该函数的内部逻辑,我就不细说了。
2. 在我的实际项目中回避了这个问题,因为我采用的是整个项目只有一个Master页面的形式,虽然开发过程中分开了不同的页面,但实际运行中,这些页面都作为web part的方式被异步获取html后,嵌入了Master的伪windows内(div实现)。所以,作为web part的页面,实际上是不需要引用.ashx的,它直接共享了Master页面已经加载的脚本。
3. .ashx是可以直接通过浏览器加载生成脚本文件的,我们可以人工下载这个文件,裁剪出需要的方法,然后手工加入到项目中,并且不再需要在script处引用.ashx了。这也是为什么每个.ashx生成脚本文件都是模板化、单层次的原因,因为这样就方便开发者自己修改这个模板,而不依赖其它。这也是本项目的潜在的最佳实践之一。