AJAX XML 项目






4.33/5 (3投票s)
本文展示了如何通过 AJAX XmlHttpRequest 对象定期刷新网页上的数据。
引言
AJAX 是一种远程脚本技术,允许浏览器调用服务器,而无需将整个页面回发到服务器。
开发 AJAX 风格的应用程序并非易事,因为它需要对 JavaScript、DOM、CSS、HTML、XML、SOAP 和相关技术有很好的了解。此外,还需要很好地掌握许多微妙的(和不那么微妙的)跨浏览器差异。
使用 ASP.NET 2.0 AJAX 服务器控件扩展,它们在后台完成所有脚本编写,您可以实现 AJAX 功能而无需编写一行 JavaScript。但是,与任何高级抽象一样,这些控件提供的灵活性不如您在原始 JavaScript 级别编程它们。本文将展示如何使用 JavaScript 和 XmlHttpRequest
对象从服务器发送或获取任何数据。
以下是该项目的业务需求:假设我们有一个电子商务应用程序,用户可以通过它在线购买一些产品,并且假设购买发生得非常频繁,因此我们必须持续在线监控该过程。
在我之前的文章中,我已经描述了一个类似项目的实现。在该项目中,实际执行所有服务器调用的页面通过 XmlHttpRequest
在 .NET 2.0 GridView
控件中返回数据。文章发表后,我收到了几条评论,指出这显然不是在客户端和服务器之间传输数据的最佳方式。
因此,该项目以一种重写,即后端页面仅以 XML 格式返回数据。然后,数据由 JavaScript 函数接收和解析。
这使得从服务器传输到客户端的数据块大小相对较小,因为页面只包含数据,没有与 ASP.NET 控件相关的任何额外代码,从而可以显著减少服务器流量。
背景
要实现 AJAX 调用,实际上需要两个网页:一个对最终用户可见,另一个实际为第一个网页生成所需内容。第一个页面通过 JavaScript 中实现的 XmpHttpRequest
对象调用第二个页面。
下面是一个非常流行的图表,可以在许多与 AJAX 相关的文章中找到。
上面的图表没有提供足够的细节来理解通过 AJAX 获取和呈现网页数据的整个过程,因此,请查看下面的图表,它显示了 AJAX 应用程序应该如何组织。
JavaScript 应该至少有两个函数:- 第一个函数应该实现回调,并将通过 XmlHttpRequest
对象从客户端请求的网页向另一个对客户端不可见但实际调用服务器以检索请求数据的网页发送异步请求。在这个示例中,服务器页面将通过业务逻辑层和数据访问层访问数据库以获取请求的数据(或者,可能是添加或删除数据),然后将响应发送回客户端网页。客户端的第二个 JavaScript 函数将把请求解析为 HTML 并将其呈现给客户端。
下面的第三个图表显示了本文描述的项目的架构。后端使用了 Northwind SQL 数据库的 Products 表,项目中添加的唯一额外功能是模拟电子商务功能:在请求从 Products 表获取数据时,添加了随机减少 Quantity 字段中数字的功能。
当数量小于再订购水平时,数据在网页上以红色显示。流式传输期结束后,数据将更新为其原始状态。
使用代码
客户端页面
本项目使用了 SQL Northwind 数据库,连接字符串在 web.config 文件中提供
<appSettings>
<add key="DSN" value="server=localhost;Integrated Security=SSPI;database=Northwind" />
</appSettings>
在此示例中,客户端页面 (Default.aspx) 有一个控件 WebControls\ProductListControl.ascx,它在网页的 Render
事件中构建表结构以保存数据。
//Get Data
DataSet ds = new DataSet();
ProductsClass.GetProducts(ds);
int rowCount = ds.Tables[0].Rows.Count;
初始数据集由 ProductClass.GetProducts(ds)
方法返回,然后构建表头和表行以呈现 DataSet
中返回的数据。当表渲染时,它通过 Write()
方法写入。
output.Write("<SPAN><ProductsList>{0}</ProductsList></SPAN>", html);
这是 Render
方法
/// <summary>
/// This method will render the web page
/// </summary>
protected override void Render(HtmlTextWriter output)
{
//Get Data
DataSet ds = new DataSet();
ProductsClass.GetProducts(ds);
int rowCount = ds.Tables[0].Rows.Count;
StringBuilder strOutput = new StringBuilder();
#region TABLE BEGIN
strOutput.Length = 0;
strOutput.Append("<TABLE cellspacing=‘0‘ cellpadding=‘1‘ " +
"border=‘0‘ class=‘dtBorder‘ width=‘700‘>\n");
#endregion TABLE BEGIN
#region Column Headers
cssHR = "dtHeaderRow";
cssHT = "dtHeaderText";
strOutput.Append("<TR class=‘").Append(cssHR).Append("‘ >\n");
strOutput.Append("<TD width=‘50‘ nowrap=‘true‘ align=‘left‘ height=‘21‘" +
" class=‘").Append(cssHT).Append("‘>").Append(
"ID").Append("</TD>");
strOutput.Append("<TD width=‘240‘ height=‘21‘ align=‘left‘ " +
"class=‘").Append(cssHT).Append("‘>").Append(
"Product Name").Append(" </TD>");
strOutput.Append("<TD width=‘200‘ height=‘21‘ align=‘light‘ " +
"class=‘").Append(cssHT).Append("‘>").Append(
"Quantity per Unit").Append(" </TD>\n");
strOutput.Append("<TD width=‘80‘ height=‘21‘ align=‘right‘ " +
"class=‘").Append(cssHT).Append("‘>").Append(
"Unit Price").Append("</TD>\n");
strOutput.Append("<TD width=‘80‘ height=‘21‘ align=‘right‘ " +
"class=‘").Append(cssHT).Append("‘>").Append(
"Quantity").Append(" </TD>\n");
strOutput.Append("<TD width=‘80‘ height=‘21‘ align=‘right‘ " +
"class=‘").Append(cssHT).Append("‘>").Append(
"Reorder Level").Append(" </TD>\n");
strOutput.Append("</TR>\n");
#endregion Column Headers
#region DISPLAY PRODUCTS
for (int i = 0; i < rowCount; i++)
{
cssDR = (i%2==0) ? "dtLightRow" : "dtDarkRow";
strOutput.Append("<TR id=‘realtimeDIV").Append(i).Append(
"‘ class=‘").Append(cssDR).Append("‘>\n");
string productId = "";
string productName = "";
string quantityPerUnit = "";
string price = "";
string quantity = "";
string reorderLevel = "";
productId = ds.Tables[0].Rows[i]["ProductId"].ToString();
productName = ds.Tables[0].Rows[i]["ProductName"].ToString();
quantityPerUnit = ds.Tables[0].Rows[i]["QuantityPerUnit"].ToString();
price = ds.Tables[0].Rows[i]["UnitPrice"].ToString();
quantity = ds.Tables[0].Rows[i]["UnitsInStock"].ToString();
reorderLevel = ds.Tables[0].Rows[i]["ReorderLevel"].ToString();
//Append Main Row with data
strOutput.Append("<TD id=‘T1").Append("_").Append(i).Append(
"‘ width=‘50‘ height=‘21‘ " +
"align=‘left‘>").Append(productId).Append("</TD>\n");
strOutput.Append("<TD id=‘T2").Append("_").Append(i).Append("‘ width" +
"=‘240‘ height=‘21‘ align=‘left‘>").Append(
productName).Append("</TD>\n");
strOutput.Append("<TD id=‘T3").Append("_").Append(i).Append(
"‘ width=‘200‘ height=‘21‘ align=‘left‘>").Append(
quantityPerUnit).Append("</TD>\n");
strOutput.Append("<TD id=‘T4").Append("_").Append(i).Append(
"‘ width=‘80‘ height=‘21‘ align=‘right‘>").Append(
price).Append("</TD>\n");
strOutput.Append("<TD id=‘T5").Append("_").Append(i).Append(
"‘ width=‘80‘ height=‘21‘ align=‘right‘>").Append(
quantity).Append("</TD>");
strOutput.Append("<TD id=‘T6").Append("_").Append(i).Append(
"‘ width=‘80‘ height=‘21‘ align=‘right‘>").Append(
reorderLevel).Append("</TD>");
strOutput.Append("</TR>\n");
}
#endregion DISPLAY PRODUCTS
#region TABLE END
strOutput.Append("<TR class=‘dtHR‘>\n");
strOutput.Append("<TD colspan=‘11‘ height=‘21‘>\n");
strOutput.Append("</TABLE>\n");
#endregion TABLE END
string html = strOutput.Replace(" ", " ").ToString();
//HtmlTextWriter output=null;
output.Write("<SPAN><ProductsList>{0}</ProductsList></SPAN>", html);
}
服务器页面
服务器页面 GetProductList.aspx 有两个方法
GetProductList()
RestoreProductList()
GetProductList()
实际上从数据源获取数据。RestoreProductList()
在 AJAX 调用完成后恢复原始数据值。
在此示例中,数据通过业务逻辑层和数据访问层(如下所述)从 MS SQL Server Northwind 数据库检索
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.Xml;
using Tirex;
public partial class GetProductsList : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Clear();
Response.ContentType = "text/xml";
string productsList = string.Empty;
string getList = "";
try
{
getList = Request.Params["getList"].ToString();
}
catch { }
if (getList == "0")
productsList = RestoreProductList();
else if (getList == "1")
productsList = GetProductList();
Response.Write(productsList);
}
private static string GetProductList()
{
//Get Data
string productsList = String.Empty;
try
{
DataSet ds = new DataSet();
ProductsClass.UpdateProducts(ds);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(ds.GetXml());
productsList = xmlDoc.InnerXml;
}
catch { }
return productsList;
}
private static string RestoreProductList()
{
//Get Data
string productsList = String.Empty;
try
{
DataSet ds = new DataSet();
ProductsClass.RestoreProductList(ds);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(ds.GetXml());
productsList = xmlDoc.InnerXml;
}
catch { }
return productsList;
}
}
另一个重要点是将页面 Contenttype
更改为 text/XML。在这种情况下,页面将以 XML 格式返回,并且不会有任何额外的 HTML 代码。
Response.Clear();
Response.ContentType = "text/xml";
业务逻辑层位于 App_Code\BLL 子文件夹中,包含 ProductsClass
类,该类有三个方法
GetProducts()
- 实际上从 Northwind 数据库的 Products 表中返回CategoryId=1
的所有产品。UpdateProducts()
- 模拟购买过程的行为 - 它更改随机选择产品的 Products 表中的UnitsInStock
值。RestoreProductlist()
- 在 AJAX 调用完成后恢复原始UnitsInStock
值。
public static void GetProducts(DataSet ds)
{
string sqlString = "SELECT * FROM Products WHERE " +
"CategoryId=1 Order By ProductName ";
SqlHelper.FillDataset(SqlHelper.connection(), CommandType.Text, sqlString,
ds, new string[] { "Products" });
}
public static void UpdateProducts(DataSet ds)
{
Random RandomClass = new Random();
int rNumber = RandomClass.Next(100);
string sqlText = "";
if (rNumber % 2 == 0 && rNumber > 0)
{
sqlText = "UPDATE Products SET UnitsInStock = " +
"UnitsInStock - 5 WHERE (ProductId = " + rNumber +
"OR ProductId = "+ rNumber/2 + ") AND CategoryId = 1";
}
else if (rNumber % 2 > 0 && rNumber > 0)
{
sqlText = "UPDATE Products SET UnitsInStock = " +
"UnitsInStock - 10 WHERE (ProductId = " + rNumber +
"OR ProductId = " + rNumber*2 + ") AND CategoryID = 1";
}
SqlParameter[] paramList = new SqlParameter[]
{
new SqlParameter("@ProductId", 1)
};
SqlHelper.ExecuteNonQuery(SqlHelper.connection(), CommandType.Text,
sqlText, paramList);
string sqlString = "SELECT * FROM Products WHERE CategoryId=1 " +
"Order By ProductName ";
SqlHelper.FillDataset(SqlHelper.connection(), CommandType.Text, sqlString,
ds, new string[] { "Products" });
}
public static void RestoreProductList(DataSet ds)
{
string sqlText = "UPDATE Products SET UnitsInStock = 100, " +
"ReorderLevel = 90 WHERE CategoryId = 1";
SqlParameter[] paramList = new SqlParameter[]
{
new SqlParameter("@ProductId", 1)
};
SqlHelper.ExecuteNonQuery(SqlHelper.connection(), CommandType.Text, sqlText, paramList);
string sqlString = "SELECT * FROM Products WHERE CategoryId=1 Order By ProductName ";
SqlHelper.FillDataset(SqlHelper.connection(), CommandType.Text,
sqlString, ds, new string[] { "Products" });
}
上述方法使用位于 App_Code\DAL 子文件夹中的数据访问层的数据访问方法,其中包含一个类 SqlHelper
。SqlHelper
类是基于开源应用程序块数据访问类创建的,包含数据访问方法的集合。它可以成功地用于从 SQL Server 数据库访问数据。
JavaScript
下面的代码包含两个分支 - 用于 IE 和 Firefox。下面的代码显示了如何为 IE 和 Firefox 浏览器创建新的 XmlHttpRequest
或 ActiveXObject("Microsoft.XMLHTTP")
对象
//branch for Firefox version
if (window.XMLHttpRequest)
{
productsListClient = new XMLHttpRequest();
}
//branch for IE/Windows ActiveX version
else if (window.ActiveXObject)
{
productsListClient = new ActiveXObject("Microsoft.XMLHTTP");
}
- 创建了
XmlHttpRequest
的实例。 - 发送服务器请求。创建布尔变量
ProductListFlag
,以仅在上次请求完成后才启动新请求。 - 服务器响应已处理。
XmlHttpRequest
对象有一个状态属性。状态属性可以有 1 到 4 的四个值,其中 4 是完整状态。
//===============================================================
//AJAX PRODUCTS LIST
//Sending information to server
function getProductsList()
{
var txtInterval = document.getElementById("txtInterval");
Minutes = txtInterval.value;
LastAccessDate = new Date();
if (ProductsListInterval != null)
{
try
{
clearInterval(ProductsListInterval);
}
catch (ex)
{
}
}
var chkViewAjax = document.getElementById("chkViewAjax");
if(chkViewAjax.checked == true)
{
AjaxServerUrl = AjaxServerUrlConst + "?getList=1";
}
else
{
AjaxServerUrl = AjaxServerUrlConst + "?getList=0";
}
ProductsListInterval = setInterval(‘ProductsListRecursion()‘, Seconds * 1000);
}
考虑我们等待响应的时间长于刷新间隔的情况。在这种情况下,设置和检查此标志值将有助于减少不必要的服务器调用次数。在服务器 URL 的参数中,添加了 currentDate
参数。回调仅在 xmlHttpRequest
的 onreadystatechange
事件为 true
时发生。如果 URL 是静态的,则回调可能永远不会被调用。
//Sending information to serverfunction ProductsListRecursion()
{
try
{
//callBack;
if (!ProductsListFlag)
return;
var currentDate = new Date();
var url=AjaxServerUrl + "&date=" + currentDate;
productsListClient.open("GET", url);
productsListClient.onreadystatechange = getProductsListBack;
ProductsListFlag=false;
productsListClient.send(null);
}
catch(ex)
{
alert(ex.message);
}
}
另一个 XmlHttpRequest
属性 status
显示 XmlHttpRequest
对象的状态,该状态应等于 200 才能继续。在下一步中,我们从数据表中获取枚举的单元格,从 XML 文档中获取 XML 节点,并将相应的节点值放入对应的数据单元格中。
如果库存中的数量值小于再订购水平值,则进行一些格式化以将行显示为红色。此示例中的所有其他代码都与在所需分钟数内停止闪烁、取消选中复选框和恢复原始数据值的功能相关。
//Waiting and processing server response
function getProductsListBack(response)
{
try
{
if(productsListClient.readyState == COMPLETE &&
productsListClient.status == OK)
{
ProductsListFlag = true;
//branch for IE/Windows ActiveX version
if (document.all)//IE
{
xmlDocument = new ActiveXObject(‘Microsoft.XMLDOM‘);
xmlDocument.async = false;
//The responseText is loaded into XML document
xmlDocument.loadXML(productsListClient.responseText);
//Get ProductNodes
var productsListNodes = xmlDocument.selectNodes(‘NewDataSet/Products‘);
var rowCount=productsListNodes.length;
//SET DATA FOR EACH ROW
//===========================================================
for (var i=0; i<rowCount; i++)
{
//Get Page Elements
//Main Table Cells
var T1="T1_"+i;
var T2="T2_"+i;
var T3="T3_"+i;
var T4="T4_"+i;
var T5="T5_"+i;
var T6="T6_"+i;
var tdT1=window.document.getElementById(T1);
var tdT2=window.document.getElementById(T2);
var tdT3=window.document.getElementById(T3);
var tdT4=window.document.getElementById(T4);
var tdT5=window.document.getElementById(T5);
var tdT6=window.document.getElementById(T6);
var tr = tdT1.parentNode;
//Get Cells Content
var xmlElementProductId=
productsListNodes[i].selectSingleNode(‘ProductID‘);
var xmlElementProductName=
productsListNodes[i].selectSingleNode(‘ProductName‘);
var xmlElementQuantityPerUnit=
productsListNodes[i].selectSingleNode(‘QuantityPerUnit‘);
var xmlElementPrice=productsListNodes[i].selectSingleNode(‘UnitPrice‘);
var xmlElementQuantity=
productsListNodes[i].selectSingleNode(‘UnitsInStock‘);
var xmlElementReorderLevel=
productsListNodes[i].selectSingleNode(‘ReorderLevel‘);
//tdT1.innerHTML = xmlElementProductId.text;
//tdT2.innerHTML = xmlElementProductName.text;
//tdT3.innerHTML = xmlElementQuantityPerUnit.text;
//tdT4.innerHTML = xmlElementPrice.text;
tdT5.innerHTML = xmlElementQuantity.text;
//tdT6.innerHTML = xmlElementReorderLevel.text;
var qty = parseInt(xmlElementQuantity.text);
var rol = parseInt(xmlElementReorderLevel.text);
if (qty < rol)
tr.style.color="#FF0000";
else
tr.style.color="#000000";
}
}
//branch for Firefox version
else if (document.implementation.createDocument)//Firefox
{
xmlDocument = new ActiveXObject(‘Microsoft.XMLDOM‘);
xmlDocument.async = false;
//The responseText is loaded into XML document
xmlDocument.loadXML(productsListClient.responseText);
//Get ProductNodes
var productsListNodes = xmlDocument.selectNodes(‘NewDataSet/Products‘);
var rowCount=productsListNodes.length;
//SET DATA FOR EACH ROW
//===========================================================
for (var i=0; i<rowCount; i++)
{
//Get Page Elements
//Main Table Cells
var T1="T1_"+i;
var T2="T2_"+i;
var T3="T3_"+i;
var T4="T4_"+i;
var T5="T5_"+i;
var T6="T6_"+i;
var tdT1=window.document.getElementById(T1);
var tdT2=window.document.getElementById(T2);
var tdT3=window.document.getElementById(T3);
var tdT4=window.document.getElementById(T4);
var tdT5=window.document.getElementById(T5);
var tdT6=window.document.getElementById(T6);
var tr = tdT1.parentNode;
//Get Cells Content
var xmlElementProductId=
productsListNodes[i].selectSingleNode(‘ProductID‘);
var xmlElementProductName=
productsListNodes[i].selectSingleNode(‘ProductName‘);
var xmlElementQuantityPerUnit=productsListNodes[i].
selectSingleNode(‘QuantityPerUnit‘);
var xmlElementPrice=productsListNodes[i].selectSingleNode(‘UnitPrice‘);
var xmlElementQuantity=
productsListNodes[i].selectSingleNode(‘UnitsInStock‘);
var xmlElementReorderLevel=
productsListNodes[i].selectSingleNode(‘ReorderLevel‘);
//tdT1.innerHTML = xmlElementProductId.text;
//tdT2.innerHTML = xmlElementProductName.text;
//tdT3.innerHTML = xmlElementQuantityPerUnit.text;
//tdT4.innerHTML = xmlElementPrice.text;
tdT5.innerHTML = xmlElementQuantity.textContent;
//tdT6.innerHTML = xmlElementReorderLevel.text;
var qty = parseInt(xmlElementQuantity.textContent);
var rol = parseInt(xmlElementReorderLevel.textContent);
if (qty < rol)
tr.style.color="#FF0000";
else
tr.style.color="#000000";
}
}
//Set date and time for LastUpdateMessages
var currentDateTime=document.getElementById("TopMessage");
var now = new Date();
currentDateTime.innerHTML=now;
var stopFlag = true;
//To Stop Streaming
for (var i=0; i<rowCount; i++)
{
//Get Quantity
var xmlElementQuantity=productsListNodes[i].selectSingleNode(‘UnitsInStock‘);
var qty = parseInt(xmlElementQuantity.text);
if (qty <100)
stopFlag = false;
}
var chkViewAjax = document.getElementById("chkViewAjax");
if(chkViewAjax.checked == false && stopFlag == true)
{
clearInterval(ProductsListInterval);
return;
}
//UpdateSession
//This will check if the streaming is happening for 2 minutes
//If yes, then streaming will be stopped and original data will be restored.
UpdateSession();
}
}
catch(err)
{
//alert(err.message);
}
}
关注点
使用上述技术,可以在没有可视化回发到服务器的情况下获取、添加、编辑、删除数据。所有回发都由对最终用户不可见的网页完成,但通过使用 XmlHttpRequest
对象的 AJAX 向客户端网页提供数据。
该项目的工作示例可以在这里查看。
历史
- 2008年12月4日:首次发布。