ajax|开发指南
概述
AJAX依靠服务器作为中介来分发和处理请求。为了完成这项工作,.net封装类依赖于客户端的请求对象,而xmlHttpRequest对象被大部分的浏览器支持,因此使用这个对象是一个不错的解决方案。因为封装的目的是隐藏xmlHttpRequest的实现,故我们不讨论他的实现细节。
封装类是通过在.net的方法上增加AJAX属性标记来实现的,一旦被标记,AJAX创建客户端的javascript函数(这类似于客户端编写的javascript函数),并使用xmlhttprequest创建服务器代理,这个代理映射客户端的函数到服务器的处理函数。
复杂吗?不会的,让我们看看下面的简单例子,给出的.net 函数
'VB.Net public function Add(firstNumber as integer, secondNumber as integer) as integer return firstNumber + secondNumber end sub |
//C# public int Add(int firstNumber, int secondNumber) { return firstNumber + secondNumber; } |
Ajax.net会立即自动的创建带有两个参数、名字为Add的javascript函数,当客户端调用这个javascript函数时,请求将从后台送到服务器端并从将计算结果返回给客户端。
初始安装
我们首先从如何把”.dll”安装到你的项目开始,当然,如果你了解如何使用,这一节可以跳过。
如果你还没有Ajax.dll,可以首先下载AJAX的最新版本。解压文件放到可以被你的项目引用的地方,在.net项目中,添加上对其的引用,然后就可以开始使用ajax.dll封装进行开发了。
如果你在安装引用时遇到了麻烦,可以参考这个链接的说明: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon/html/vbtskaddingremovingreferences.asp |
设置HttpHandle
为了使其可以工作,第一步必须做的是在web.config中安装设置封装包的HttpHandle,不去详细解释HttpHandle是如何工作的,我们只需要了解他们可以用来处理asp.net请求。例如,所有的目的为*.aspx的请求可以通过System.Web.UI.PageHandlerFactory类发送到控制句柄,简单的说,我们把任何向ajax/*.ashx的请求发送到Ajax.PageHandlerFactory的请求处理句柄:
<configuration> <system.web> <httpHandlers> <add verb="POST,GET" path="ajax/*.ashx" type="Ajax.PageHandlerFactory, Ajax" /> </httpHandlers> ... <system.web> </configuration> |
上面的代码告诉asp.net把任何匹配到特定的路径(ajax/*.ashx)请求发送到Ajax.PageHandlerFactory产生的HttpHandle,而不再是默认的Handler factory。你不需要创建ajax子目录,这是一个只用来临时使用的虚拟的目录,因此别的HttpHandler可以用他们自己的目录来使用.ashx扩展名的文件。
配置页面
现在我们准备好开始代码编写了。打开一个新的网页或者已经存在的页面,在其codebehind文件中的Page_Load事件中增加以下代码:
'vb.net Public Class Index Inherits System.Web.UI.Page Private Sub Page_Load(sender As Object, e As EventArgs) Handles MyBase.Load Ajax.Utility.RegisterTypeForAjax(GetType(Index)) '... end sub '... End Class |
//C# public class Index : System.Web.UI.Page{ private void Page_Load(object sender, EventArgs e){ Ajax.Utility.RegisterTypeForAjax(typeof(Index)); //... } //... } |
对RegisterTypeForAjax方法的调用在页面产生如下的javascript代码(另外一种选择,你也可以人工在页面上添加如下的javascript代码)
<script language="javascript" src="ajax/common.ashx"></script> <script language="javascript" src="ajax/NAMESPACE.PAGECLASS,ASSEMBLYNAME.ashx"></script> |
上面这段代码的粗体部分NAMESPACE.PAGECLASS,ASSEMBLYNAME含义如下:
NAMESPACE.PAGECLASS | 当前页面的命名空间和类 |
ASSEMBLYNAME | 当前页面的程序集的名称 |
下面是在AjaxPlay项目中sample.aspx的示例输出:
<% Page Inherits="AjaxPlay.Sample" Codebehind="sample.aspx.cs" ... %> <html> <head> <script language="javascript" src="ajax/common.ashx"></script> <script language="javascript" src="ajax/AjaxPlay.Sample,AjaxPlay.ashx"></script> </head> <body> <form id="Form1" method="post" runat="server"> ... </form> </body> </html> |
你可以测试一下,人工通过浏览器将src path(通过查看源文件并copy)打开,一切都能正常的工作。如果输出了无意义的文本表示到目前为止是正确的,如果输出asp.net错误,则表示中间出现了错误。
即使你不了解HttpHandle的工作方式,也应该可以理解上面的描述。通过web.config,我们可以确保发送向ajax/*.ashx的请求由我们自定义的句柄来处理,很显然,两个脚本标记由自定义句柄处理。
编写服务端函数
现在我们编写服务器端函数,他们可以被客户端异步的调用。尽管现在还不能支持全部的返回类型,我们仍坚持服务器端添加功能。在codebehind文件的页面类里,添加下面的方法:
'VB.Net <Ajax.AjaxMethod()> _ Public Function ServerSideAdd (byval firstNumber As Integer, byval secondNumber As Integer) As Integer Return firstNumber + secondNumber End Function |
//C# [Ajax.AjaxMethod()] public int ServerSideAdd(int firstNumber, int secondNumber) { return firstNumber + secondNumber; } |
注意,这个函数有Ajax.AjaxMethod()定制属性,属性服务会告知ajax封装类为此方法创建一个javascript代理,这样才能被客户端调用。
定制客户端调用
接下来在客户端用javascript调用函数。Ajax封装类会创建一个javascript函数,带有两个参数,名字是 类名.ServerSideAdd。作为最基本的功能,我们所需要作的只是调用这个方法并且传递参数:
<% Page Inherits="AjaxPlay.Sample" Codebehind="sample.aspx.cs" ... %> <html> <head> <script language="javascript" src="ajax/common.ashx"></script> <script language="javascript" src="ajax/AjaxPlay.Sample,AjaxPlay.ashx"></script> </head> <body> <form id="Form1" method="post" runat="server"> <script language="javascript"> var response = Sample.ServerSideAdd(100,99); alert(response.value); </script> </form> </body> </html> |
当然,我们不能把这么强大的功能仅仅用来通过alert来提醒浏览者,这就是为什么所有的客户端代理(如 类名.ServerSideAdd函数)同时带有一个额外的定制属性。这个属性是用来处理服务器响应的回调函数:
Sample.ServerSideAdd(100,99, ServerSideAdd_CallBack); function ServerSideAdd_CallBack(response){ if (response.error != null){ alert(response.error); return; } alert(response.value); } |
从上面的代码中可以看出,我们为ServerSideAdd函数增加了一个额外参数ServerSideAdd_CallBack,这个参数就是用来处理服务器端响应的客户端函数。这个callback函数接受一个带有四个关键属性的response对象:
value | 服务器端函数执行的返回值(可能是一个字符串、自定义对象或者dataset) |
error | 如果发生错误,则返回错误信息. |
request | 原始的xmlHttpRequest请求 |
context | 一个上下文对象 |
我们首先应该检查是否有错误发生,你可以通过在服务器端函数抛出异常来实现这个error属性。在上面这个例子中,我们简单的alert了一个值,就是value属性;request属性可以用来取得额外的信息(见下面的表格)
如果你想了解更多的关于XmlHttpRequest的知识,可以查看下面的链接: http://www.quirksmode.org/blog/archives/2005/02/xmlhttp_linkdum.html |
处理类型
返回一个复杂类型
Ajax可以支持除了我们上面ServerSideAdd函数返回的Int值以外很多类型。他可以直接支持integers, strings, double, booleans, DateTime, DataSets 和 DataTables,也支持简单的自定义类型和数组。其他的类型通过其ToString方式来返回字符串。
返回DataSet的工作就像真正的.net Dataset.给出一个返回DataSet的服务器端函数,我们可以通过下面的方法在客户端显示:
<script language="JavaScript"> //Asynchronous call to the mythical "GetDataSet" server-side function function getDataSet(){ AjaxFunctions.GetDataSet(GetDataSet_callback); } function GetDataSet_callback(response){ var ds = response.value; if(ds != null && typeof(ds) == "object" && ds.Tables != null){ var s = new Array(); s[s.length] = "<table border=1>"; for(var i=0; i<ds.Tables[0].Rows.length; i++){ s[s.length] = "<tr>"; s[s.length] = "<td>" + ds.Tables[0].Rows[i].FirstName + "</td>"; s[s.length] = "<td>" + ds.Tables[0].Rows[i].Birthday + "</td>"; s[s.length] = "</tr>"; } s[s.length] = "</table>"; tableDisplay.innerHTML = s.join(""); } else{ alert("Error. [3001] " + response.request.responseText); } } </script> |
Ajax也可以支持自定义类,但是需要这个类是可以被序列化的。如下面的类:
[Serializable()] public class User{ private int _userId; private string _firstName; private string _lastName; public int userId{ get { return _userId; } } public string FirstName{ get { return _firstName; } } public string LastName{ get { return _lastName; } } public User(int _userId, string _firstName, string _lastName){ this._userId = _userId; this._firstName = _firstName; this._lastName = _lastName; } public User(){} [AjaxMethod()] public static User GetUser(int userId){ //Replace this with a DB hit or something :) return new User(userId,"Michael", "Schwarz"); } } |
我们需要通过调用RegisterTypeForAjax向服务器注册GetUser代理:
private void Page_Load(object sender, EventArgs e){ Utility.RegisterTypeForAjax(typeof(User)); } |
在客户端我们可以通过这样的方式调用GetUser函数:
<script language="javascript"> function getUser(userId){ User.GetUser(GetUser_callback); } function GetUser_callback(response){ if (response != null && response.value != null){ var user = response.value; if (typeof(user) == "object"){ alert(user.FirstName + " " + user.LastName); } } } getUser(1); </script> |
返回值同服务器端对象一样有三个属性(FirstName, LastName and UserId)。
译者注:其他的类型只能由开发者通过在服务器端函数在返回值时自定义转换为ajax支持的类型来实现了,ajax推荐使用ToString方法。
其他工作方式
在其他类注册函数
在上面的例子及描述中,我们都是通过在页面的codebehind文件里完成函数的注册,但并不是说只能在页面的codebehide文件里完成注册,我们也可以在其他类中注册函数。回忆一下,Ajax封装类是通过在特定类里面查找那些有Ajax.AjaxMethod()属性的方法来完成工作的,这些类在客户端又通过两个script片断来完成返回值描述。使用Ajax.Utility.RegisterTypeForAjax,我们可以得到任何我们想得到类的详细内容。例如,下面的例子可以说明我们在其他类中使用服务器端函数是合法的:
Public Class AjaxFunctions <Ajax.AjaxMethod()> _ Public Function Validate(username As String, password As String) As Boolean 'do something 'Return something End Function End Class |
不过我们需要首先在调用页面注册这个代理类,类的名字不再是页面类,而是我们使用的这个类:
'Vb.Net Private Sub Page_Load(sender As Object, e As EventArgs) Handles MyBase.Load Ajax.Utility.RegisterTypeForAjax(GetType(AjaxFunctions)) '... End Sub |
//C# private void Page_Load(object sender, EventArgs e){ Ajax.Utility.RegisterTypeForAjax(typeof(AjaxFunctions)); //... } |
记住,客户端调用使用这种格式的名字<ClassName>.<ServerSideFunctionName>。因此,如果上面的Serversideadd函数位于AjaxFunctions类,而不是页面类的话,客户端调用则变为:AjaxFunctions.ServerSideAdd(1,2)
代理是怎样工作的呢?
第二个script标签,如下面的示例
<script type="text/javascript" src="/cqyd/ajax/cqyd.SchemeSendWatch,cqyd.ashx"></script>
是由Ajax utility通过命名空间、类以及页面程序集自动生成的(当然也可以人工加入),从这一点我们可以想到Ajax.PageHandlerFactory是通过反射来取得有定制属性的函数的细节。很显然,Handler寻找带有AjaxMethod定制属性的函数,取得他们的特征(返回类型、名称、参数)并依据这些信息创建客户端代理。特别的,ajax创建一个与类型相同的JavaScript对象作为代理。
返回Unicode字符
Ajax.net可以从服务器端向客户端返回Unicode字符,为了做到这一点,在服务端函数返回时返回的值必须是Html编码的:
[Ajax.AjaxMethod] public string Test1(string name, string email, string comment){ string html = ""; html += "Hello " + name + " "; html += "Thank you for your comment <b>"; html += System.Web.HttpUtility.HtmlEncode(comment); html += "</b>."; return html; } |
SessionState
在服务端函数,你可能需要接受传送的session信息,为了做到这一点,必须要在想实现这个方式的服务端函数的Ajax.AjaxMethod属性上传递一个参数。在查看ajax可以支持session的时候,我们先看看其他的特征。在下面这个例子中,我们有一个文档管理系统,当一个用户对文档进行编辑的时候会给这个文档加锁,其他用户需要等到这个文档可用时才能修改。不使用Ajax,用户需要不断等待刷新,因为不得不不断的去检查文档的状态是否为可用,这当然不是一个很好的方案。用ajax的session state支持,这就比较容易了。
我们首先写一个函数,这个函数通过遍历文档ID找到用户需要的文档,存储到session里,并返回没有占用的文档:
'Vb.Net <Ajax.AjaxMethod(HttpSessionStateRequirement.Read)> _ Public Function DocumentReleased() As ArrayList If HttpContext.Current.Session("DocumentsWaiting") Is Nothing Then Return Nothing End If Dim readyDocuments As New ArrayList Dim documents() As Integer = CType(HttpContext.Current.Session("DocumentsWaiting"), Integer()) For i As Integer = 0 To documents.Length - 1 Dim document As Document = document.GetDocumentById(documents(i)) If Not document Is Nothing AndAlso document.Status = DocumentStatus.Ready Then readyDocuments.Add(document) End If Next Return readyDocuments End Function |
//C# [Ajax.AjaxMethod(HttpSessionStateRequirement.Read)] public ArrayList DocumentReleased(){ if (HttpContext.Current.Session["DocumentsWaiting"] == null){ return null; } ArrayList readyDocuments = new ArrayList(); int[] documents = (int[])HttpContext.Current.Session["DocumentsWaiting"]; for (int i = 0; i < documents.Length; ++i){ Document document = Document.GetDocumentById(documents[i]); if (document != null && document.Status == DocumentStatus.Ready){ readyDocuments.Add(document); } } return readyDocuments; } } |
我们在属性参数中指明了HttpSessionStateRequirement.Read(还可以是Write and ReadWrite)
下面写javascript函数来使用这个方法带来的结果:
<script language="javascript"> function DocumentsReady_CallBack(response){ if (response.error != null){ alert(response.error); return; } if (response.value != null && response.value.length > 0){ var div = document.getElementById("status"); div.innerHTML = "The following documents are ready!<br />"; for (var i = 0; i < response.value.length; ++i){ div.innerHTML += "<a href=\"edit.aspx?documentId=" + response.value[i].DocumentId + "\">" + response.value[i].Name + "</a><br />"; } } setTimeout('page.DocumentReleased(DocumentsReady_CallBack)', 10000); } </script> <body > |
页面加载后每10秒钟向服务器函数请求一次。如果有返回,则call back函数检查response,并把最新的结果显示出来。
结论
Ajax技术可以给客户端提供丰富的客户体验,而ajax.net为您容易的实现这样强大的功能提供了可能。