11 月21日晚7点应邀在武汉大学信息学院做了一场关于 ASP.NET 下 AJAX 开发的报告,以我自己经历讲述了一些特殊应用在过去到现在实现手段的变化,本来想回家之后做个总结的,但是由于最近以来事情一直很多,所以没有来得及总结。今晚得以有空总结一下。
AJAX 介绍
其实 AJAX 应用的核心就是 XMLHttpRequest ,通过现象看本质,即使使用微软的 AJAX 服务器控件最终也是需要这些的,只不过使用微软 AJAX 服务器控件开发 AJAX 应用时我们不需要关心 JS 脚本的实现,只需关心业务逻辑就可以了,因而可以简化开发和提高开发速度。 AJAX 的基础是 XHTML 、 CSS 、 DOM 、 JavaScript 、 XML 及 XMLHttpRequest 。
正确使用 AJAX 技术可以改善用户体验,是用户与服务器的交互更流畅,某些情况下还能减少服务器流量。在以前 AJAX 只是作为一种比较炫的技术为一些大型网站所使用,现今这个比云计算还要流行了,至少云计算更多地还是停留在人们的概念里,而 AJAX 确确实实应用在 WEB 开发当中了。 WEB 开发人员的招聘都是言必精通 AJAX 技术。
下面分别讲讲在 ASP.NET 开发中可以供选择的开发 AJAX 应用的方式:
采用纯 JavaScript 实现
在武侠小说中绝顶高手飞花摘叶都可以伤人,在 WEB 开发领域真正的高手也可以无需借助任何其它库就可以开发出 AJAX 应用。不适用任何第三方库开发 AJAX 应用就需要自己区分浏览器来实例化 XMLHttpRequest 对象实例,下面的代码是一个简单的调用 AJAX 的代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head> <title>获取服务器时间的例子</title> <mce:script language="javascript" type="text/javascript"><!-- var request=false; //实例化XMLHttpRequest function createXMLHttpRequest() { try { //下面的方法有可能抛出异常,表示当前浏览器不支持此方法 request=new ActiveXObject("Msxml2.XMLHTTP"); } catch(e1)//当通过上面的方法实例XMLHttpRequest发生异常 { try { //下面的方法有可能抛出异常,表示当前浏览器不支持此方法 request=new ActiveXObject("Microsoft.XMLHTTP"); } catch(e2)//当通过上面的方法实例XMLHttpRequest发生异常 { request=false; } } //当上面的方法都不能实例化XMLHttpRequest时,表示非IE浏览器 if(!request&&typeof XMLHttpRequest!='undefined') { //非IE浏览器实例化XMLHttpRequest的方法 request=new XMLHttpRequest(); } } //发送客户端请求的方法 function getServerTime(format) { //调用上面的方法实例化XMLHttpRequest createXMLHttpRequest(); //要请求的URL地址,注意escape是javascript中的方法 //用于对特殊字符进行转义 var url="ServerTime.aspx?format="+escape(format); //alert(url);//可以通过这种方法查看服务器的返回结果 //通过GET方式打开请求,第三个参数true表示异步发送请求,false则为同步 request.open("GET",url,true); //当request的等待状态发生变化时要执行的客户端方法 request.onreadystatechange=update;//update是客户端的javascript方法 //因为在请求的url中已经附带了参数,所以这里的参数是null //表示不再发送额外的数据 request.send(null); } //当接收到服务器的响应之后执行的客户端方法 function update() { if(request.readyState==4) { //alert(request.responseText);//查看服务器响应结果 document.getElementById("time").innerHTML=request.responseText; } } // --></mce:script> </head> <body> <table border="0"> <tr> <td>服务器时间</td><td><div id="time"></div></td> </tr> <tr> <td><input type="button" value="完整时间" onclick="javascript:void getServerTime('yyyy-mm-dd hh:mm:ss');" /></td><td><input type="button" value="年月日" onclick="javascript:void getServerTime('yyyy-MM-dd');" /></td> </tr> <tr> <td><input type="button" value="时分秒" onclick="javascript:void getServerTime('hh:mm:ss');" /></td><td><input type="button" value="月份日期" onclick="javascript:void getServerTime('mm-dd');" /></td> </tr> </table> </body> </html>
注意 XMLHttpRequest.readyState 共有5种状态,其可能值和对应描述如下:
0 :请求未初始化,还没有调用 open() 。
1 :请求已经建立,但是还没有发送,还没有调用 send() 。
2 :请求已发送,正在处理中(通常现在可以从响应中获取内容头)。
3 :请求在处理中;通常响应中已有部分数据可用了,没有全部完成。
4 :响应已完成;您可以获取并使用服务器的响应了。
从上面的代码中可以看出每次实例化 XMLHttpRequest 对象都需要判断,一些常用的操作也可以封装一下,利用 Prototype 这个 JavaScript 脚本库就可以轻松做到这一点,实际上早期很多人就用到了 Prototype 来开发 AJAX 应用,并且在 Prototype 中还封装了其它很多通用的方法,大大提高了我们的开发效率。
使用 Prototype
在 Prototype 中提供了一个 Ajax 对象,这样开发人员就可以直接使用 Ajax 对象而不必考虑如何判断浏览器类型再决定如何实例化 XMLHttpRequest 对象的实例了。下面的代码是使用了 Protype 之后的代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>使用Prototype获取服务器时间的例子</title> <mce:script type="text/javascript" language="javascript" src="prototype.js" mce_src="prototype.js"></mce:script> <mce:script language="javascript" type="text/javascript"><!-- function getServerTime(format) { //要请求的URL地址,注意escape是javascript中的方法 //用于对特殊字符进行转义 var url = "ServerTime.aspx"; var params = "format=" + escape(format); var ajax = new Ajax.Request( url, { method: 'get', parameters:params, onComplete:update } ); } //当接收到服务器的响应之后执行的客户端方法 function update(request) { $("time").innerHTML = request.responseText; } // --></mce:script> </head> <body> <table border="0"> <tr> <td>服务器时间</td><td><div id="time"></div></td> </tr> <tr> <td><input type="button" value="完整时间" onclick="javascript:void getServerTime('yyyy-mm-dd hh:mm:ss');" /></td><td><input type="button" value="年月日" onclick="javascript:void getServerTime('yyyy-MM-dd');" /></td> </tr> <tr> <td><input type="button" value="时分秒" onclick="javascript:void getServerTime('hh:mm:ss');" /></td><td><input type="button" value="月份日期" onclick="javascript:void getServerTime('mm-dd');" /></td> </tr> </table> </body> </html>
从上面的代码可以看出使用了一些相对较为成熟的 JavaScript 框架之后可以使我们的代码大大减少,开发速度也得到了提高。
使用jQuery
当然我们还可以使用目前比较热门的 JavaScript 框架 jQuery ,这个在 VS2008 中需要安装 SP1 后才能活得智能提示,在 VS2010 中已经集成了。如果使用 jQuery 上面的代码可以简化为:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>使用jQuery获取服务器时间的例子</title> <mce:script src="jquery-1.2.6-cn.js" mce_src="jquery-1.2.6-cn.js" type="text/javascript"></mce:script> <mce:script type="text/javascript" language="javascript"><!-- function getTime() { $.get("GetTime.aspx", { format: "yyyy-mm-dd hh:mm:ss" }, function(data) {//得到Ajax响应后的回调函数 //$("#time").append("<font color='red'>" + data + "</font>"); $("#time").html("<font color='red'>" + data + "</font>"); }); } // --></mce:script> </head> <body> <table border="0"> <tr> <td>服务器时间</td><td><div id="time"></div></td> </tr> <tr> <td><input type="button" value="完整时间" onclick="javascript:void getServerTime('yyyy-mm-dd hh:mm:ss');" /></td><td><input type="button" value="年月日" onclick="javascript:void getServerTime('yyyy-MM-dd');" /></td> </tr> <tr> <td><input type="button" value="时分秒" onclick="javascript:void getServerTime('hh:mm:ss');" /></td><td><input type="button" value="月份日期" onclick="javascript:void getServerTime('mm-dd');" /></td> </tr> </table> </body> </html>
可以看出在 jQuery 中提供了更多、更灵活的处理 AJAX 和 XHTML 的方法,简易大家都去了解一下。
在上面的方式中无论使用自己写全部 JavaScript 脚本还是利用 Protype 或者 jQuery 这类框架的方式,都是需要写一些 JS 脚本的,有没有尽可能少写脚本的方式呢?毕竟我们知道 JS 脚本调试起来相对较为困难些。答案是有的,那就是使用 AjaxPro 。
使用 AjaxPro
下面是一个使用 AjaxPro 的例子,设计代码如下:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Register.aspx.cs" Inherits="day13_Register" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>带Ajax效果的用户注册页面</title> <mce:script language="javascript" type="text/javascript"><!-- function checkUserName() { var userName=document.getElementById("txtUserName").value; //alert(userName); //注意day13_Register是Register.aspx页面对应的class名称 //也就是在这里通过JavaScript调用服务器上的方法并接受返回结果 var result=day13_Register.CheckUserName(userName).value; document.getElementById("msg").innerHTML=result; } // --></mce:script> </head> <body> <form id="form1" runat="server"> <div> <table border="0" width="100%"> <tr> <td>帐号</td><td> <asp:TextBox ID="txtUserName" runat="server" onblur="checkUserName()"></asp:TextBox><div id="msg"></div> <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="txtUserName" Display="Dynamic" ErrorMessage="*"></asp:RequiredFieldValidator> <asp:RegularExpressionValidator ID="RegularExpressionValidator1" runat="server" ControlToValidate="txtUserName" Display="Dynamic" ErrorMessage="必须是4到14个字母或数字" ValidationExpression="[/w]{4,14}"></asp:RegularExpressionValidator></td> </tr> <tr> <td>真实姓名</td><td> <asp:TextBox ID="txtRealName" runat="server"></asp:TextBox><asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" ControlToValidate="txtRealName" Display="Dynamic" ErrorMessage="*"></asp:RequiredFieldValidator><asp:RegularExpressionValidator ID="RegularExpressionValidator2" runat="server" ControlToValidate="txtRealName" ErrorMessage="姓名是2到4个汉字" ValidationExpression="[/u4e00-/u9fa5]{2,4}"></asp:RegularExpressionValidator></td> </tr> <tr> <td>年龄</td><td> <asp:DropDownList ID="ddlAge" runat="server"> </asp:DropDownList></td> </tr> <tr> <td>性别</td><td> <asp:RadioButton ID="rbMale" runat="server" Checked="True" GroupName="sex" Text="男" /> <asp:RadioButton ID="rbFemale" runat="server" Text="女" /></td> </tr> <tr> <td>手机</td><td> <asp:TextBox ID="txtMobile" runat="server"></asp:TextBox> <asp:RegularExpressionValidator ID="RegularExpressionValidator3" runat="server" ControlToValidate="txtMobile" Display="Dynamic" ErrorMessage="格式不对" ValidationExpression="((13[0-9])|(15[0-9]))/d{8}"></asp:RegularExpressionValidator></td> </tr> <tr> <td>电话</td><td> <asp:TextBox ID="txtPhone" runat="server"></asp:TextBox> <asp:RegularExpressionValidator ID="RegularExpressionValidator4" runat="server" ControlToValidate="txtPhone" Display="Dynamic" ErrorMessage="格式不对" ValidationExpression="(/(/d{3,4}/)|/d{3,4}-)?/d{7,8}"></asp:RegularExpressionValidator></td> </tr> <tr> <td>电子邮件</td><td> <asp:TextBox ID="txtEmail" runat="server"></asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator3" runat="server" ControlToValidate="txtEmail" Display="Dynamic" ErrorMessage="*"></asp:RequiredFieldValidator> <asp:RegularExpressionValidator ID="RegularExpressionValidator5" runat="server" ControlToValidate="txtEmail" Display="Dynamic" ErrorMessage="格式不对" ValidationExpression="/w+([-+.']/w+)*@/w+([-.]/w+)*/./w+([-.]/w+)*"></asp:RegularExpressionValidator></td> </tr> <tr> <td></td><td> <asp:Button ID="btnRegister" runat="server" Text="注册" OnClick="btnRegister_Click" /> <input id="Reset1" type="reset" value="重置" /></td> </tr> </table> </div> </form> </body> </html>
后台逻辑代码如下:
using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Data.SqlClient; using AjaxPro; public partial class day13_Register : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { //用于给年龄下拉列表增加选项 for (int i = 18; i < 101; i++) { ddlAge.Items.Add(i.ToString()); } //注册Type AjaxPro.Utility.RegisterTypeForAjax(typeof(day13_Register), this); } } /// <summary> /// 检查是否存在该用户名,由AjaxPro调用的方法必须有AjaxMethod属性 /// </summary> /// <param name="userName">要检查的用户名</param> /// <returns></returns> [AjaxMethod] public string CheckUserName(string userName)//这个方法是提供给前台js调用的 { bool exist = false; //读取事先在web.config中的数据库连接信息 string connectionString = ConfigurationManager.ConnectionStrings["Conn"].ConnectionString; //定义查询语句,在这里依然使用一直使用着的[UserInfo]表 string sql = "select UserName from UserInfo where UserName=@UserName"; SqlConnection connection = new SqlConnection(connectionString); SqlCommand command = new SqlCommand(sql, connection); command.Parameters.AddWithValue("@UserName", userName); connection.Open(); //CommandBehavior.CloseConnection枚举表示关闭DataReader时也会关闭关联的Connection SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection); //如果DataReader含有一行或多行,表示存在对应的用户名 if (reader.HasRows) { exist = true; } reader.Close(); if (exist) { return string.Format("<font color='red'>{0}这个用户名已经被注册。</font>", userName); } else { return string.Format("<font color='green'>{0}这个用户名暂未被注册。</font>", userName); } } protected void btnRegister_Click(object sender, EventArgs e) { string userName = txtUserName.Text; string realName = txtRealName.Text; int age = int.Parse(ddlAge.SelectedValue); bool male = rbMale.Checked; string mobile = txtMobile.Text; string phone = txtPhone.Text; string email = txtEmail.Text; string sql = "insert into UserInfo(UserName,RealName,Age,Sex,Mobile,Phone,Email)" + "values(@UserName,@RealName,@Age,@Sex,@Mobile,@Phone,@Email)"; string connectionString = ConfigurationManager.ConnectionStrings["Conn"].ConnectionString; SqlConnection connection = new SqlConnection(connectionString); SqlCommand command = new SqlCommand(sql, connection); command.Parameters.AddWithValue("@UserName",userName); command.Parameters.AddWithValue("@RealName",realName); command.Parameters.AddWithValue("@Age", age); command.Parameters.AddWithValue("@Sex", male); command.Parameters.AddWithValue("@Mobile", mobile); command.Parameters.AddWithValue("@Phone", phone); command.Parameters.AddWithValue("@Email", email); connection.Open(); //返回受影响的行数 int count = command.ExecuteNonQuery(); connection.Close(); //如果受影响的行数大于0,表示增加成功 if (count > 0) { WriteScript("alert('注册成功!');window.location='register.aspx'", "success"); } else { WriteScript("alert('注册失败!')", "fail"); } } /// <summary> /// 向客户端输出javascript脚本 /// </summary> /// <param name="script"></param> /// <param name="key"></param> private void WriteScript(string script,string key) { ClientScriptManager cs=Page.ClientScript; if(!cs.IsClientScriptBlockRegistered(key)) { cs.RegisterClientScriptBlock(this.GetType(),key,script,true); } } }
从上面的代码中可以看出使用 AjaxPro 之后可以不用写太多客户端代码,而且不同前面几种做法需要写一个页面调用显示,一个页面负责控制业务逻辑(一般情况下是这样的),使用 AjaxPro 可以将调用页面和控制业务逻辑页面合在一起,这样服务端代码调试比较方面,而且也不用控制如何发送请求,请求什么时候响应完毕。
使用 AjaxPro 有几方面需要注意,就上面的代码中的注意事项说明如下:
Page_Load事件中:
JS脚本中:
网页中调用的服务器端方法:
当然在运行上面的代码之前需要对web.config做些配置,打开web.config文件,在<configuration><system.web>后添加以下代码(如果已经存在<httpHandlers>节点那就直接添加<add>部分内容即可):
<httpHandlers> <add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro"/> </httpHandlers>
上面这段代码的作用是是保证客户端向"ajaxpro/*.ashx"的请求(POST和GET)都被AjaxPro.AjaxHandlerFactory拦截。
使用微软 AJAX 控件库
前面几种开发 AJAX 应用的方式都是遵循一个规律:开发速度是有低到高,开发难易程度由难到易,不过在前面几种方式中不管怎么做都还没有完全脱离 JavaScript ,这个东东对于现在的一些初学者(就是那些感觉做 ASP.NET 开发就只是拖拽控件)来说还是有些难度,还是需要写 JavaScript 来操作 HTML 页面,为了彻底减轻这部分人掌握 ASP.NET 中 AJAX 开发,微软推出了 AJAX 服务器端控件。
使用微软 AJAX 服务器端控件可以在做简单设置的情况下满足开发环境中的大部分简单需求,当然做一些稍微复杂点的设置就可以满足开发环境中的绝大部分需求了。
在微软 AJAX 服务器控件库中主要有以下几个服务器控件:
ScriptManager 控件:注册脚本,必须在所有服务器控件之前出现
ScriptManagerProxy 控件:参考其它脚本和服务
UpdatePanel 控件:局部刷新控件
Timer 控件:定时执行控件
UpdateProgress 控件:进度显示控件
下面是一个使用了微软 AJAX 服务器控件的例子:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Register.aspx.cs" Inherits="Register" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>用户注册</title> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server"> </asp:ScriptManager> <table border="0"> <tr><td>用户名</td><td> <asp:UpdatePanel ID="UpdatePanel2" runat="server"> <ContentTemplate> <asp:TextBox runat="server" ID="txtUserName" AutoPostBack="True" ontextchanged="txtUserName_TextChanged"></asp:TextBox> <asp:Label ID="lbStatus" runat="server"></asp:Label> </ContentTemplate> <Triggers> <asp:AsyncPostBackTrigger ControlID="txtUserName" EventName="TextChanged" /> </Triggers> </asp:UpdatePanel> </td></tr> <tr><td>密码</td><td><asp:TextBox runat="server" ID="txtPwd" TextMode="Password"></asp:TextBox></td></tr> <tr><td>确认密码</td><td><asp:TextBox runat="server" ID="txtConfirmPwd" TextMode="Password"></asp:TextBox></td></tr> <tr><td>找回密码提示问题</td><td><asp:TextBox runat="server" ID="txtPwdQuestion"></asp:TextBox></td></tr> <tr><td>找回密码提示答案</td><td><asp:TextBox runat="server" ID="txtPwdAnswer"></asp:TextBox></td></tr> <tr><td>籍贯</td><td> <asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <asp:DropDownList ID="ddlProvince" runat="server" AppendDataBoundItems="True" AutoPostBack="True" DataTextField="ProvinceName" DataValueField="ProvinceID" onselectedindexchanged="ddlProvince_SelectedIndexChanged"> <asp:ListItem Value="0">请选择</asp:ListItem> </asp:DropDownList> 省<asp:DropDownList ID="ddlCity" runat="server" AutoPostBack="True" DataTextField="CityName" DataValueField="CityID" onselectedindexchanged="ddlCity_SelectedIndexChanged"> </asp:DropDownList> 市<asp:DropDownList ID="ddlRegionalism" runat="server" AutoPostBack="True" DataTextField="RegionalismName" DataValueField="RegionalismID"> </asp:DropDownList> 县(区) </ContentTemplate> <Triggers> <asp:AsyncPostBackTrigger ControlID="ddlProvince" EventName="SelectedIndexChanged" /> <asp:AsyncPostBackTrigger ControlID="ddlCity" EventName="SelectedIndexChanged" /> <asp:AsyncPostBackTrigger ControlID="ddlRegionalism" EventName="SelectedIndexChanged" /> </Triggers> </asp:UpdatePanel> </td></tr> <tr><td> </td><td> <asp:Button ID="btnRegister" runat="server" Text="注册" onclick="btnRegister_Click" /> <input id="Reset1" type="reset" value="reset" /></td></tr> </table> </form> </body> </html>
这个页面的作用其实很简单,就是在用户打开这个注册页面填写完自己的用户名之后准备填写其它信息(就是鼠标从用户名文本框移开后)时就能获知自己的用户名是否已经被注册,还有另外一个效果,就是在用户注册时实现了省(直辖市)、市(区县)、县三级联动的效果,因为使用了微软 AJAX 控件,所以操作过程中整个页面不会全部刷新,只有局部刷新效果。
在使用微软 AJAX 服务器控件过程中需要注意一些事项,就上面的例子中需要注意的事项如下:
总结 :
其实这篇文章是新瓶装旧酒(和 AJAX 一样),里面用到的代码都是以前在博客文章中出现的,这篇文章只不过做了一个综合性的比较而已。
对于上面出现的这么多选择,我们不必一味去追问哪种比哪种一定要好,实际上很多技术都有自己的使用场合的,没有绝对在所有领域所有场合都是最佳的(那就好像找一匹不吃草且跑得快的马)。控制越灵活的需要对脚本知识掌握越高,开发速度也相对低;开发速度越高、调试越容易的控制起来相对较难一些(就像武学一样,绝对高手飞花摘叶可以杀人,花花草草找起来很简单,但是没有深厚的功力不行;倚天屠龙即使在普通人手里也可以让他威力大增,但是不是每个人都有这种机会)。
对于初学者或者初级开发人员我建议如下:
如果开发简单 AJAX 应用,使用 AJAX 控件首选,无需任何脚本知识;
如果开发复杂 AJAX 应用,建议使用 jQuery ,这是一个可以用于多种动态网页编程的 Javascript 脚本库。