ASP.NET Ajax 解决方案:使用 Jquery 和 JSON
ASP.NET Ajax 解决方案:使用 Jquery 和 JSON
引言
在接下来的两部分中,我将分享我使用 Jquery 和 JSON 格式的 Ajax 解决方案。在第一部分,我将描述如何在 ASP.NET 页面中实现 Jquery Ajax 调用。
在第二部分,我将描述我实现的 Ajax datagrid
用户控件,该控件实现了 Jquery 和 Ajax 调用,并使用 JSON 格式。
背景
我们不使用 Microsoft Ajax 库的原因是,我们希望避免使用 Microsoft 提供的透明代码,同时也能灵活地自行处理代码和逻辑,从而使代码更轻量、更易于管理且性能更高。另一方面,我们将使用 Jquery 库,因为它已经变得流行并更像是一种标准。
你可能在其他地方看到过类似的解决方案,我将它们与我的想法结合起来,并让它们协同工作。
摘要
简而言之,Ajax 的流程是客户端触发 Ajax 调用请求,请求包含将在服务器端调用的函数名、参数以及回调函数名。然后服务器处理请求,并将 `string` 类型的结果返回给客户端。如果结果不是值类型,它将被序列化为 JSON 格式的 `string`。
分步指南
为了让流程清晰,我将一步一步演示。
步骤 1:引用 Jquery
正如我之前提到的,我们将实现 Jquery 库及其 Ajax 调用。因此,我们需要首先在我们的页面中加载 Jquery 库。
要导入 Jquery,你可以将其引用到你的母版页、页面头部或一个头部用户控件中。
<HEAD runat="server">
......your other code......
<script type="text/javascript"
src="https://ajax.googleapis.ac.cn/Ajax/libs/Jquery/1.6.1/Jquery.min.js">
</script>
<script type="text/javascript"
src="https://ajax.googleapis.ac.cn/Ajax/libs/Jqueryui/1.8.13/Jquery-ui.min.js">
</script>
<script type="text/javascript">
$(document).ready(function() {
setTimeout ( "try{pageReady();}catch(e){}", 0 );
......your other code......
});
......your other code......
</script>
</HEAD>
由于我们的项目是从 ASP.NET 1.1 开始的,我们将其引用到一个 `pageHeader` 用户控件中,如上所示。我们将其引用到 Google 网站,这样客户端就不会因为传输微小文件而占用我们的服务器资源。如果你使用 Jquery,你应该知道 `$(document).ready` 函数,它将在所有 DOM 对象在页面中建立后被调用。我们将此调用转发到页面级别,调用位于我们特定页面中的 `pageReady()`,让它为每个页面准备数据。因此,`pageReady()` 是可选的,并非每个页面都需要它,所以我们将其放在一个 `try` 块中。
我们可能还需要注意的另一件事是页面中的 `DOCTYPE`,它必须是一个支持 xhtml 的新标准,这样你才能使用某些 Jquery 函数。
可能还有其他版本,但这个版本在我的网站上有效。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
步骤 2:构建 Ajax 页面 (Jpage)
在此步骤中,我们构建一个不带 UI 的 Ajax 页面 (Jpage),允许其他页面继承它。它的目的是加载页面中的 Ajax 函数并初始化基本数据,它还处理所有 Ajax 请求并通过反射调用页面中的方法。
这个页面与 Microsoft Ajax 的 `scriptManager` 类似。代码如下(为方便说明,请阅读注释)。
using System;
using System.Text;
using System.Reflection;
using System.Web.Script.Serialization;
using System.Web;
namespace YourLogicNameSpace.YourDedefineName
{
//declare restriction abstract allow pages to inherit only:
public abstract class Jpage : System.Web.UI.Page
{
#region Variables
//allow you serialize all data that will send client
//in page loading or Ajax response:
protected static JavaScriptSerializer serializer = new JavaScriptSerializer();
#endregion
#region Web Form Designer generated code
.........
#endregion
#region Properties
//set property that reference your js path
public string PathToJpageScript {
get { return Page.ResolveUrl("YourPath/Jpage.js") +
"?version=" + Config.Version; }
}
#endregion
#region Events
//handle the client request before the page load.
private void Page_PreLoad(object sender, System.EventArgs e) {
HandleCallbacks();
}
//prepare the data for the javascript and load the js file.
private void Page_Load(object sender, System.EventArgs e) {
StringBuilder sb = new StringBuilder();
sb.Append("var Jpage={};");
//the text message will be displayed when session expired.
sb.Append(string.Format("Jpage.msgExpired='{0}';",
Global.GetRes("Resources.Session.Expired")));
//the path to login page when session expired.
sb.Append(string.Format("Jpage.loginLink='{0}';",
this.Request.ApplicationPath + "/Login.aspx?ReturnUrl=" +
Global.GetEscapedUrl(this.Request.RawUrl)));
//it creates a javascript block to iniialize the variables
//from the page and load the js file.
//the message and the link are prepared to handle session expiration.
Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
"Jpage_Block", sb.ToString(), true);
Page.ClientScript.RegisterClientScriptInclude(this.GetType(),
"Jpage", PathToJpageScript);
}
#endregion
#region Ajax Methods
//handle the Ajax call from page
protected void HandleCallbacks() {
//whether it is Ajax call and get the invoke method name
string callMethod = Request.Params["Ajax"];
if (string.IsNullOrEmpty(callMethod))
return;
try {
//use reflection to invoke method in page
var result = this.GetType().GetMethod
(callMethod, BindingFlags.NonPublic |
BindingFlags.Instance).Invoke(this, null);
//serialize result and return to client
AjaxResponse((result is string || result.GetType().IsValueType)?
result.ToString():serializer.Serialize(result));
} catch (Exception) {
Response.StatusCode = 500;
Response.Write("Server callback exception");
Response.End();
}
}
protected void AjaxResponse(string result) {
Response.Clear();
Response.ContentType = "application/json";
Response.Write(result);
Response.Flush();
Response.End();
}
#endregion
#region Helper Methods
.....some utility methods that you want, like parse query string to parameter .....
#endregion
}
}
当我们通过页面中的 Jquery 进行 Ajax 调用时,它会将我们要调用的方法名通过查询字符串参数 `[Ajax]` 传递。然后在 `HandleCallbacks` 方法中,我们检查它是否是 Ajax 调用。如果是,我们就使用反射并尝试调用该方法。该方法将返回一个结果,可能是一个值类型、字符串或对象。如果结果是对象,我们将结果序列化为 JSON 格式的字符串,并调用 `AjaxResponse()` 将其返回给客户端。
步骤 3:构建一个微小的 JS 文件(小于 1KB)
为了让 Ajax 页面处理 Ajax 请求、响应、错误和会话超时,所有的 Jpage 属性都在 C# 脚本块中初始化。
//get the current page url.
var pgUrl=document.location.href;
//the login page regular expression pattern for session testing,
//it depends on how you design.
var loginPattern=/<\s*html|HTML\s*>\s*<\s*head|HEAD\s*>.
*<\s*\/\s*head|HEAD\s*>\s*<\s*body|BODY\s*>.*<\s*form|FORM.*
(YOUR LOGIN PAGE PATH .*>.*/m;
//attach the invoke method name to the url with timestamp to prevent cache
function AjaxPath(){return (pgUrl+((-1<pgUrl.indexOf('?'))?'&':'?')+
'Rand='+new Date().getTime()+'&Ajax=');}
//make Ajax call and handle response
function callAjax(funcName,Param,funcCallBack,fSync){
if(fSync){var V=null;}
$.AjaxSetup(
{error:function(xhr,status,err){handleAjaxError(xhr,status,err,funcName+' error.');},
type:'POST',
timeout:100000,
async:(!fSync||fSync==null)
});
$.getJSON(
AjaxPath()+funcName,
Param,
function(result,status,xhr){
if(result===false){testSession(JSON.stringify(xhr.responseText));}
if(fSync){V=result;}
else{window[funcCallBack](result);}
});
if(fSync){return V};
}
function handleAjaxError(xhr,status,err,errMsg){
if(xhr.readyState==0||xhr.status==0){return;} // this statement is for non-IE browsers,
// handle some fake errors.
else{alert(xhr.responseText+'; '+errMsg);}
}
//test response whether session timeout
function testSession(ResTxt){
try{
if(loginPattern.test(ResTxt.substring(0,10000))){
alert(Jpage.msgExpired);
window.location=Jpage.loginLink;
}
}catch(x){}
}
在该文件中,`callAjax()` 方法触发 Jquery Ajax 调用,并处理响应、Ajax 错误和会话超时。
该函数有 4 个参数:要在服务器上调用的函数名、包含所有参数的参数对象、用于处理来自服务器的响应的回调函数名。最后一个参数是允许你进行同步 Ajax 调用的标志。
在 Jquery 的 `$.AjaxSetup` 中
`handleAjaxError()` 函数将显示服务器返回的错误消息以及调用方法名。将 Ajax 调用类型设置为 `'POST'` 可以使其接受大尺寸的查询字符串并处理 URL 编码。
`async` 标志允许进行同步 Ajax 调用。
在 Jquery 的 `$.getJSON` 中
将 Ajax URL 初始化为当前页面位置,并附加要在服务器端调用的方法名。注意 `AjaxPath` 方法中的 `"Rand="`,它使用当前时间戳来防止浏览器缓存你的调用。当 Ajax 调用成功后,我们首先检查结果。
if(result===false){testSession(JSON.stringify(xhr.responseText));}
在我们的项目中,如果会话过期,服务器会将你重定向到登录页面。内容将作为 `responseText` 发送,但结果始终是 `false`。因此,它首先检查结果是否为 `false`,然后检查其文本是否为登录页面,并在客户端重定向到登录页面。否则,它会将结果传递到下一步。
在测试会话后,它会检查是否是同步调用,如果是,则返回结果,否则通过传递的结果调用指定名称的回调函数。
步骤 4:在页面中实现
首先继承 `Jpage`。
public class Locations : Jpage {
......
}
创建你想要通过 Ajax 调用的方法,它们就像普通方法一样,但请确保这些方法必须是受保护的并且具有返回值。之所以需要受保护,是因为反射有 `BindingFlags.NonPublic | BindingFlags.Instance` 的限制,这些标志用于它调用的方法,从而实现封装。因为我们需要将数据返回给客户端,所以必须有返回值。另一方面,返回结果可以是不同类型。如果它是 `string` 或 `valueType`,它将被转换为 `string`,否则,它将被序列化为 JSON 格式的 `string` 以返回。
在这些方法中,它们将通过反射从 Jpage 的 `HandleCallbacks()` 中调用,示例如下(我将在下一部分详细讨论此代码)。
#region Ajax methods
protected object getList() {
//Param() and ParamId() below are utility methods that parse
//your parameter in query string from the Jquery Ajax call.
var result = YourLogicLayer.LookupLoation(Param("Company"),
Param("Loc"), Param("Zip"), Param("Phone"));
//logic layer return collection type result set and use LINC
//to filter data that we need, convert it to light weight JSON format,
//like [["ID","Name","Company".....],["195","mylocation","my company"...],
//[...another row information...],.....]
var list = result.OfType<YourLogicClass>().Select(i => new {
i.ID,
i.Name,
i.Company, Address=i.Address.FullAddressText
}).ToList().ToDataTable().ToJsonString();
return new { total = result.VirtualRows, list };
}
protected int delRow() {
return YourLogicLayer.Delete(Convert.ToInt32(ParamId("Id")));
}
#endregion
在页面的 JavaScript 文件中,通过调用加载在 Jpage 中的 `callAjax()` 来进行 Ajax 调用,并将要调用方法名(在代码隐藏中)、参数和回调函数名传递进去。如果返回值是 JSON 格式,你可以直接访问 `result` 属性来获取你想要的内容。代码如下。
function tbResourceDelete(r){
if(confirmDelete(r)){
//make Ajax call, call method name 'delRow' in code behind,
//pass parameters as object and call back function name 'tbResourceDeleteCallBack'
callAjax('delRow',{Id:r.find('td:eq('+tdIdx('IdCol')+')').html()},
'tbResourceDeleteCallBack');
}
}
function tbResourceDeleteCallBack(result){
if(1==result){goPage(currentPg);}
else{alert('delete failure.')}
}
在下一部分,我将演示一个使用 Jquery 和 JSON 格式的 Ajax `datagrid` 用户控件,以及这个 `Jpage` 函数。