Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

An AJAX XML Project

4.33/5 (3 votes)
4 Dec 2008CPOL7 min read 26.8K   279  
The article shows how to periodically refresh the data on a web page through the AJAX XmlHttpRequest object.

AjaxXmlProject

Introduction

AJAX 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 XmlHttpRequest object.

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 GridView control through XmlHttpRequest. After the article was published, I received several comments that this was not obviously the best way of transferring data between the client and the server.

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.

Background

To 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 XmpHttpRequest object implemented in JavaScript.

Below is a very popular diagram that can be found in many AJAX related articles.

D1.jpg

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 XmlHttpRequest object from the web page requested by the client to another web page that is not visible to the client but does the actual calls to the server to retrieve the requested data. In this sample, the server page will go through all the paths in the Business Logic Layer and the Data Access Layer to the database to get the requested data (or, may be to add or delete data), and then will send a response back to the client web page. The second JavaScript function on the client side will parse the request into HTML and present it to the client.

D2.jpg

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.

D3.jpg

Using the code

Client page

The SQL Northwind database was used for this project, the connection string is provided in the web.config file:

XML
<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 Render event.

C#
//Get Data
DataSet ds = new DataSet();
ProductsClass.GetProducts(ds);
int rowCount = ds.Tables[0].Rows.Count;

The initial dataset is returned by the ProductClass.GetProducts(ds) method, and then the table header and a table row to present the data returned in the DataSet are built. When the table is rendered, it is written with the Write() method.

C#
output.Write("<SPAN><ProductsList>{0}</ProductsList></SPAN>", html);

Here is the Render method:

C#
/// <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(" ", "&#160;").ToString();
    //HtmlTextWriter output=null;
    output.Write("<SPAN><ProductsList>{0}</ProductsList></SPAN>", html);
}

Server page

The server page, GetProductList.aspx, has two methods:

  • GetProductList()
  • RestoreProductList()

GetProductList() actually gets the data from the data source. RestoreProductList() restores the original data values once the AJAX calls are completed.

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:

C#
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 Contenttype of the page to text/XML. In this case, the page will be returned as XML, and will not have any additional HTML code.

C#
Response.Clear();
Response.ContentType = "text/xml";

The Business Logic Layer is located in the App_Code\BLL subfolder, and contains the ProductsClass class that has three methods:

  • GetProducts() - actually returns all the products for CategoryId=1 from the Products table in the Northwind database.
  • UpdateProducts() - simulates the behavior of the purchase process - it changes the UnitsInStock value in Products table for a randomly selected product.
  • RestoreProductlist() - restores the original UnitsInStock values once the AJAX calls are completed.
C#
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 SqlHelper. The SqlHelper class was created based on an open source Application Blocks data access classes, and contains a collection of data access methods. It can be successfully used to access data from a SQL Server database.

JavaScript

The code below contains two branches - for IE and Firefox. The code below shows how the new XmlHttpRequest or ActiveXObject("Microsoft.XMLHTTP") objects should be created for the IE and Firefox browsers:

C#
//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");
}
  1. An instance of the XmlHttpRequest is created.
  2. JavaScript
    //===============================================================
    //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);   
    }
  3. A server request is sent. A boolean variable, ProductListFlag, is created to start the new request only after the previous request is completed.
  4. 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 currentDate parameter is added. The callback happens only when the onreadystatechange event of the xmlHttpRequest is true. If the URL is static, there is a chance that the callback will never be called.

    JavaScript
    //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);
     }
    }
  5. The server response is processed. The XmlHttpRequest object has a state property. The state property may have four values from 1 to 4 where 4 is a complete state.
  6. Another XmlHttpRequest property, status, shows the status of the XmlHttpRequest object which should be equal to 200 in order to proceed. In the next step, we get the enumerated cells from a data table, the XML nodes from the XML document, and put the appropriate node values into the corresponding data cells.

    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.

JavaScript
//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 interest

Using 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 XmlHttpRequest object.

A working sample of this project can be viewed here.

History

  • 4th December, 2008: Initial post.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)