|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionAJAX is a remote scripting technique that allows the browser to call the server without posting the entire page back to the server. Developing AJAX-style applications is not simple as it requires a good knowledge of JavaScript, DOM, CSS, HTML, XML, SOAP, and related technologies. In addition, it is necessary to have a good grasp of the many subtle, and not-so-subtle, cross-browser differences. With ASP.NET 2.0 AJAX Server Control Extensions that do all the scripting behind, you can implement AJAX functionality without writing a line of JavaScript. But, as with any high-level abstraction, those controls offer less flexibility than if you were to program them on a raw JavaScript level. This article will show how to send or get any data from the server using JavaScript and the Here are the business requirements for this project: Let’s say, we have an Ecommerce application from which users are able to buy some products online, and let’s say that purchases are happening very often, so that we have to constantly monitor the process online. In my previous article, I have already described the implementation of a similar project. In that project, the page that actually did all the calls to the server returned the data within a .NET 2.0 So, the project was rewritten in a way that the page behind returns just the data in XML format. The data is then received and parsed by a JavaScript function. This makes the size of the chunks of data that are transferred from the server to the client relatively small as the page contains only data and has no any additional code related to ASP.NET controls, and thus the server traffic can be significantly reduced. BackgroundTo implement AJAX calls, you actually need to have two web pages: the one that is visible to the end user, and the one that actually generates the required content for the first web page. The first page calls the second one through an Below is a very popular diagram that can be found in many AJAX related articles.
The above diagram does not provide enough details to understand the whole process of getting and presenting data on a web page through AJAX, so, please review the diagram below that shows how the AJAX application should be organized. The JavaScript should have at least two functions: - the first one should implement a callback and will send an asynchronous request through the
The third diagram below shows the architecture of the project that is described in this article. The Northwind SQL database, Products table was used at the back end, and the only extra functionality that was added to the project was to simulate the Ecommerce functionality: in the request to get the data from the Products table, the functionality to randomly decrease the numbers in the Quantity field was added. When quantity becomes less than the Reorder level, the data is shown in red on a web page. After the streaming period is finished, the data is updated to its original state.
Using the codeClient pageThe SQL Northwind database was used for this project, the connection string is provided in the web.config file: <appSettings>
<add key="DSN" value="server=localhost;Integrated Security=SSPI;database=Northwind" />
</appSettings>
In this sample, the client page (Default.aspx) has a control, WebControls\ProductListControl.ascx, which builds the table structure to hold the data in a web page //Get Data
DataSet ds = new DataSet();
ProductsClass.GetProducts(ds);
int rowCount = ds.Tables[0].Rows.Count;
The initial dataset is returned by the output.Write("<SPAN><ProductsList>{0}</ProductsList></SPAN>", html);
Here is the /// <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);
}
Server pageThe server page, GetProductList.aspx, has two methods:
In this sample, the data are retrieved from an MS SQL Server Northwind database through the Business Logic Layer and the Data Access Layer, which are described below: 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;
}
}
Another important point is to change the Response.Clear();
Response.ContentType = "text/xml";
The Business Logic Layer is located in the App_Code\BLL subfolder, and contains the
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" });
}
The above methods use the data access methods from the Data Access Layer which is located in the App_Code\DAL subfolder, and contains a class JavaScriptThe code below contains two branches - for IE and Firefox. The code below shows how the new //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");
}
//===============================================================
//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);
}
Consider the case when we wait for a response longer than a refreshing interval. In such a case, setting and checking this flag value will help to reduce the number of unnecessary calls to the server. In the parameters of the server URL, the //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);
}
}
Another Some formatting is done to show the rows in red if the quantity in the stock value is less than the Reorder level value. All other code in this sample is related to the functionality to stop flashing in the required number of minutes, uncheck the checkbox, and restore the original data values. //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);
}
}
Points of interestUsing the above technique, it is possible to get, add, edit, delete data without visual postbacks to server. All postbacks are done by a web page that is not visible to the end user, but provides the data to the client web page through AJAX using the A working sample of this project can be viewed here. History
|
||||||||||||||||||||||