AJAX技术经过这么多年的发展,出现了一些框架或类库用于简化开发工作,不同的框架类库的使用方法也各不相同。 现在,再回头看看这些技术,看看这些框架类库,我们能感受到技术在不断地发展,AJAX开发越来越容易了。
本文收集了在ASP.NET平台上,一些具体代表性的AJAX开发方法,我将用实际的示例代码来演示如何使用它们, 让您感受AJAX的进化历程,同时也希望将一些优秀的AJAX开发方法介绍给您。
为了方便地介绍这些AJAX开发方法,我将它们划分为四代技术。
注意:按代划分AJAX技术纯属我个人的观点,只为了更好了区分它们。
此外,一些不借助任何框架类库的原始AJAX开发方法,本文将不讨论它们。
第一代技术:生成客户端代理脚本调用服务端
这类技术展示了第一代的AJAX框架的主要设计思想:在服务端为客户端生成代理脚本, 然后由这些代理脚本调用服务端,调用者可以不必知道整个调用过程是如何实现的, 而且在客户端的调用风格也基本与服务端的代码类似。
这类技术的代表作有:ASP.NET AJAX, AjaxPro 二个服务端框架。
下面我将用ASP.NET AJAX框架来演示如何进行AJAX开发。
首先,我们可以创建一个WebService服务:
[WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] //若要允许使用 ASP.NET AJAX 从脚本中调用此 Web 服务,请取消对下行的注释。 [System.Web.Script.Services.ScriptService] public class WebService1 : System.Web.Services.WebService { [WebMethod] public int Add(int a, int b) { return a + b; }
这段代码就是一个普通的WebService代码,唯独需要注意的是:在类的定义上加了一个ScriptService修饰特性。
接下来,我们还需要在一个ASPX页面中,用ScriptManager为它生成客户端的代理脚本:
<asp:ScriptManager ID="ScriptManager1" runat="server"> <Services> <asp:ServiceReference Path="/WebService1.asmx" InlineScript="true" /> </Services> </asp:ScriptManager>
说明:InlineScript="true"的设置并不是必须的,只是为了让我们看到ScriptManager到底生成了什么代码。
从截图可以看到,除了引入了二个必要的AJAX客户端类库外,还在客户端为WebService1生成了代理脚本。
有了这些代码后,我们可以用下面的JavaScript代码调用服务端:
function Call_Add(){ WebService1.Add(1,2, ShowResult); } function ShowResult(result){ document.getElementById("output").value = result; }
前面这个示例太简单了,再来个参数复杂的示例吧,还是从先服务端开始,先定义一个参数类型:
public class Customer { public string Name { get; set; } public int Age { get; set; } public string Address { get; set; } public string Tel { get; set; } public string Email { get; set; } }
WebSerice的代码:
[WebMethod] public string AddCustomer(Customer customer) { if( customer == null ) return "customer is null."; // 简单地返回一个XML字符串。 // 告诉客户端:服务端收到了什么样的数据。 return XmlHelper.XmlSerialize(customer, Encoding.UTF8); }
仍然借用前面的ScriptManager设置,来看JavaScript的调用代码:
function Call_AddCustomer(){ var customer = {Name: document.getElementById("txtName").value, Age: document.getElementById("txtAge").value, Address: document.getElementById("txtAddress").value, Tel: document.getElementById("txtTel").value, Email: document.getElementById("txtEmail").value}; WebService1.AddCustomer(customer, ShowResult); }
基本上还是与服务端的编码方式差不多,先创建一个customer对象,再传给调用方法。
在那个年代之前(2006年),原始的AJAX实现方式非常复杂,而这种方法让客户端代码风格看起来与服务端十分类似,这确实是个了不起的设计思路。 然而各种技术一直在改进中,现在,2013年,当我们再来回顾这种方法时,会发现它确实存在一些不完善的地方, 以至于现在使用这种方法的人很少了,这种技术被淘汰了!
其实我们可以从另一外角度来思考:如果这种方法真的很优秀,它就不可能被淘汰,正是因为有更优秀的方法出现了,它才会遭到淘汰的命运。
新技术的改进方向
前面介绍的那种AJAX方法能让客户端的调用代码与服务端的代码风格基本一致,看似很完美的方法为什么会被淘汰了呢?
我来分析一下这种开发方法的缺陷:
1. 前端代码不够独立,必须要在页面中添加引用之后才能调用服务端,这其实是一种强耦合。
2. 出现了更优秀的前端框架能简少获取调用参数的代码量。
继续阅读本文,您会发现后面我将要介绍的新方法都朝着解决这些缺陷在努力, 这些缺陷也算是指出了新技术的改进方向。
由于前端在调用服务端时,需要事先生成代理脚本,这种设计会阻碍前端代码的封装性。 您可以想象一下:如果客户端需要一个【获取当前用户信息】的功能,而这个功能必须由服务端实现的, 此时,我们就只能指望服务端为客户端生成代理类来调用这个功能了。 但这个功能太有用,许多地方都需要使用,您是不是会想将它提取到一个公用的文件中?
遗憾的是:就算您将这段调用代码提取到一个公共的public.js文件中,每个页面在引用public.js后, 并不能调用【获取当前用户信息】功能,因为代理脚本并不一定存在,public.js中的代码还不能运行起来。 怎么办?
答:为每个引用public.js的页面,再添加ScriptManager引用那个服务吧。
共用性越高的功能,您会发现这种引用代码的重复度也就越高。
简单说来,这种方法将WebService, aspx页面, js代码耦合在一起了。
由于耦合,您越用越发现越麻烦。
这种生成代理脚本的开发方法虽然能让前端代码与后端代码的风格一致,然而, 前端与后端毕竟不是同一种开发语言,它们要关注的方向也是不一样的。尤其是当更优秀的前端框架出现后, 这种后端包办前端的方法不仅让后端与前端耦合在一起,而且还限制了前端技术的发展, 最终只能是被抛弃的命运!
现在请记住我们为了提交一个Customer信息写了什么样的代码:
var customer = {Name: document.getElementById("txtName").value, Age: document.getElementById("txtAge").value, Address: document.getElementById("txtAddress").value, Tel: document.getElementById("txtTel").value, Email: document.getElementById("txtEmail").value};
我在介绍第四代技术时,您会发现它们消失了!
第二代技术:jQuery直接调用WebService
随意jQuery前端类库的流行,另一种新的开发方法也开始流行起来了。
HTTP调用本来是个很简单很透明的技术,只要指定一个URL,构造一个请求体就可以了, 前端代理脚本的方法将这个过程封装了起来,由于它的封装制造了耦合并限制前端的发展。 新的AJAX技术只能突破这个限制,舍弃这些代理脚本,直接调用后端代码。
下面的示例代码还是基于前面的示例,唯独不同的是:不是需要代理类,现在是直接调用服务端。
由于后端的服务代码没有改变,我也就不再贴出它们了,而且页面也不需要再添加什么引用,我们就直接看前端代码好了:
$.ajax({ type:"POST", url: "/WebService1.asmx/Add", contentType:"application/json", data:"{a: 1, b: 2}", dataType:'json', success:function(result){ $("#output").val(result.d); } });
这段代码也能调用服务端的Add方法。
由于服务端采用JSON数据格式,所以需要在客户端多指定一些请求头,而这些事情以前是由代理脚本完成的。 虽然现在的代码稍微多一点,但是耦合没有了,更便于提取一些公用代码了。
事实上,如果您一直用这种方法调用WebService,那么jQuery提供了设置默认参数的功能, 我们可以利用这个特性减少代码量。
还是再来看一下前面那个复杂的参数类型的前端调用代码吧:
var customer = {Name: $("#txtName").val(), Age: $("#txtAge").val(), Address: $("#txtAddress").val(), Tel: $("#txtTel").val(), Email: $("#txtEmail").val()}; var jsonStirng = $.toJSON( {customer: customer} ); $.ajax({ type:"POST", url: "/WebService1.asmx/AddCustomer", contentType:"application/json", data: jsonStirng, dataType:'json', success:function(result){ $("#output").val(result.d); } });
主要的代码还是一样的,集中在获取调用参数,但是要转成JSON格式。
再次一次提醒:不要老是盯着要指定一大堆的jQuery参数,它们可以通过设置默认值的方式解决。
我之所以现在不想让它们消失,是因为后面还有更好的方法,先留着它们吧。
说明:这种方法不仅可以用于调用WebService,也可以调用WCF (basicHttpBinding),毕竟它们都使用HTTP协议。 不过,WCF还有一堆烦人的配置要设置,但这不是jQuery的问题,这是服务端框架的缺陷。
第三代技术:更简单的数据格式
前面我们看到了可以利用jQuery调用WebService,不过JSON的转换过程感觉有些多余,浏览器的提交就没有这个转换步骤嘛。 有时看到一些家伙们着还在JavaScript中拼接JSON字符串,我非常反感,所以这次的示例代码并没有给那种方法抹黑,我采用了一个JSON插件。
第三代技术就完美地解决了输入输出必须采用JSON问题,而且解决了POST的限制。
由于这次变革改变了数据格式,所以服务端也发生了改变, 新的框架解决了这些问题,例如:ASP.NET MVC框架,MyMVC框架都支持这个开发方式。
来看一下现在服务端的代码:
[Action] public int Add(int a, int b) { return a + b; } [Action] public string AddCustomer(Customer customer) { // 简单地返回一个XML字符串。 // 告诉客户端:服务端收到了什么样的数据。 return XmlHelper.XmlSerialize(customer, Encoding.UTF8); }
注意:这种AJAX技术没有与客户端的任何耦合,只要知道一个URL就可以调用了。 来看客户端的代码吧:
$.ajax({ type:"POST", url: "/AjaxDemo/Add.cspx", data: {a: 1, b: 2}, success:function(result){ $("#output").val(result); } }); // 第二个调用 var customer = {Name: $("#txtName").val(), Age: $("#txtAge").val(), Address: $("#txtAddress").val(), Tel: $("#txtTel").val(), Email: $("#txtEmail").val()}; $.ajax({ type:"POST", url: "/AjaxDemo/AddCustomer.cspx", data: customer, success:function(result){ $("#output").val(result); } });
注意:type:"POST"并不是必须的,您也可以把它们改成GET方式提交。
如果您此时用Fiddler查看请求内容,会发现请求的数据采用的是key=value&key=vlaue的格式,与浏览器的方式一致。 由于没有JSON数据格式的限制,现在的参数项简单了。
现在再看上面这段代码,主要代码量在哪里?
是不是在获取调用参数那块?
继续阅读本文,我要让它消失。
第四代技术:直接提交表单
我们来看一下示例用的表单:
<form id="form1" action="/AjaxDemo/AddCustomer.cspx" method="post"> <p><b>新增客户资料</b></p> <span>Name: </span><input type="text" name="Name" value="abc" /><br /> <span>Age: </span><input type="text" name="Age" value="20" /><br /> <span>Address: </span><input type="text" name="Address" value="武汉" /><br /> <span>Tel:</span> <input type="text" name="Tel" value="12345678" /><br /> <span>Email: </span><input type="text" name="Email" value="test@163.com" /><br /> <br /> <input type="submit" name="btnAddCustomer" value="保存客户资料" /> </form>
前面用了三种方法在提交这个表单,下面我们再来看一下更简单的提交方式:
<script type="text/javascript"> $(function(){ // 只需要下面这个调用就可以将表单改成异步提交方式! $("#form1").ajaxForm({ success:function(result){ $("#output").val(result); } }); }); </script>
为了更清楚展示这种方法,我甚至把script标签也贴出来了。
如果您用过jQuery就应该能发现,真正的代码就只有ajaxForm的那个调用。
说明:ajaxForm是jQuery.form插件提供的功能。
服务端的代码继续使用前面示例的代码,所以就不贴出了。
再对比前面几种AJAX的实现方法,您说哪种方法最简单?
您对第四代AJAX技术有兴趣吗?
我还为它设计了三种不同场景下的示例,让您感受它的强大与简单,请继续阅读。
多submit按钮的提交(用jQuery.form实现)
您认为前面的示例太简单了,是吗?
可能有人会说,如果有多个submit按钮,这种方法就不合适了,我要响应每个按钮,为它们指定不同的URL !
真是这样吗? 看下面的示例吧。
相关的前端代码如下:
<form id="form1" action="/AjaxTestAutoAction/submit.cspx" method="post"> <p><span>Input:</span> <input type="text" name="input" style="width: 300px" value="Fish Li" /></p> <p><span>Output:</span> <input type="text" id="output" style="width: 300px" readonly="readonly" /></p> <input type="submit" name="Base64" value="转换成Base64编码" /> <input type="submit" name="Md5" value="计算md5" /> <input type="submit" name="Sha1" value="计算sha1" /> </form> <script type="text/javascript"> $(function(){ $("#form1").ajaxForm(function(result) { $("#output").val(result); }); }); </script>
服务端代码:
public class AjaxTestAutoAction { [Action] public string Base64(string input) { return Convert.ToBase64String(Encoding.Default.GetBytes(input)); } [Action] public string Md5(string input) { byte[] bb = Encoding.Default.GetBytes(input); byte[] md5 = (new MD5CryptoServiceProvider()).ComputeHash(bb); return BitConverter.ToString(md5).Replace("-", string.Empty); } [Action] public string Sha1(string input) { byte[] bb = Encoding.Default.GetBytes(input); byte[] sha1 = (new SHA1CryptoServiceProvider()).ComputeHash(bb); return BitConverter.ToString(sha1).Replace("-", string.Empty); } }
代码仍然很清晰:
1. 服务端定义三个方法,对应三个submit按钮。
2. 前端还是只调用一个ajaxForm解决所有问题。
这种方法就是由前端的 jQuery, jQuery.form 以及服务端的MyMVC框架 共同实现的。 想像一下利用其它三种方法需要多少代码吧。
批量输入控件的提交(用jQuery.form实现)
再来展示另一个现实的例子,批量输入界面的提交。
JavaScript代码:
<script type="text/javascript"> $(function(){ $("#form1").ajaxForm({ success:function(result){ $("#output").val(result); } }); });
这个示例的全部代码就这么多,废话不想多说,您自己去想用其它方法需要多少代码!
提交复杂表单(用jQuery.form实现)
前面的示例都是直接提交表单,没有验证表单的过程,而且都以Textbox控件为主,再来个复杂的表单示例。
JavaScript代码:
<script type="text/javascript"> $(function(){ $("#form1").ajaxForm({ beforeSubmit: ValidateForm, success:function(result){ $("#output").val(result); } }); function ValidateForm(formData, jqForm, options) { if( jqForm.context.ProductName.value.length == 0 ){ alert("商品名称不能为空。"); $(jqForm.context.ProductName).focus(); return false; } return true; } }); </script>
服务端代码:
[Action] public string AddProduct(Product product) { // 简单地返回一个XML字符串。 // 告诉客户端:服务端收到了什么样的数据。 return XmlHelper.XmlSerialize(product, Encoding.UTF8); }
各种AJAX开发方法的对比与总结
看过了这些示例后,我们再来回顾这些AJAX方法的进化过程:
1. 以ASP.NET AJAX为代表的【生成客户端代理脚本调用服务端】技术, 为了包装原始AJAX的复杂过程,服务端为客户端生成了代理脚本, 这种封装后的客户端在调用方式上与服务端基本一致,看起来简化了不少,而且降低了开发门槛, 然而,它的优点也是它是它的最大缺点:服务端包办了客户端的事情,它完全没有想到客户端技术也在进步! 当更优秀的前端技术出现时,它的结局只能是淘汰。
2. 【jQuery直接调用WebService】可以看做是第一代技术的改进,它丢弃了服务端生成代理脚本的功能, 直接在客户端准备服务端所需要的数据格式,借助jQuery内部对XmlHttpRequest的封装,也能方便地调用服务端。 这种方法解决了客户端、服务、页面三方的耦合,但是数据格式却受到序列化方式的限制,使得起来怪怪的。 这种怪怪的感觉其实也是不方便的表现。
3. 为了让前端更方便地调用服务端,服务端框架只能改变,ASP.NET MVC框架和MyMVC框架都支持更简单的数据格式, 而且不需要那个多余的asmx文件,直接用类库就可以响应客户端请求。 这类技术的最主要特点是:用更简单的数据格式就可以调用服务端。 由于数据格式简单化,为以后的继续改进留下了机会。
4. 由于服务端不要求序列化的数据格式,且前端技术仍在进步,终于jQuery.form插件可以让我们不需要关注表单数据的收集过程, 它能模拟浏览器的提交行为,正确识别【成功控件】并将它们提交到服务端,因此代码得到最大限度的缩减,让AJAX开发过程更加容易。
前面几个采用jQuery.form的示例也让我们看到:不管表单是什么样的,永远只需要一个调用。
而且它将jQuery的调用过程也做了非常好的封装,所以我认为这是最容易使用的AJAX开发方法。
相关链接
1. jQuery: http://jquery.com/
2. jQuery.form: http://jquery.malsup.com/form/
3. MyMVC: http://www.cnblogs.com/fish-li/archive/2012/02/12/2348395.html