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

Simple AJAX, ASP.NET, and C# implementation for a page wait or process screen

4.67/5 (30 votes)
26 Sep 2008CPOL6 min read 1  
A simple AJAX approach that includes an HTML page client, and a JavaScript making an AJAX call and receiving a response. A remote page will receive the parameter information and respond to the JavaScript call method with the necessary information.

Introduction

It is very common that we come across an action item, or a use case, or a screen interface that requires a process status, and the general development strategy is to show a please-wait icon/image on the screen, and the page gets post back once the process is completed. This article/document is designed to help the developer to make a real time visual process page to the user, giving a rich user interface. I will discuss a very simple approach of implementing this using AJAX in an ASP.NET web application. And, the good thing is that you do not have to install or upgrade your existing development environment to an AJAX Framework or use extenders, and this can be implemented with the existing XMLHttpRequestBrowser object.

Most of you already know that AJAX stands for Asynchronous JavaScript and XML. To my best knowledge, Microsoft used to refer this as DHTML in the past, and later this concept/technology become popular as AJAX. I am sure you have come across AJAX in your development process or in various online articles. Simply type AJAX at any search engine, and it will bring back tons of articles related to AJAX and its implementation, which is not the scope of this article/document.

Brief Description

This is a simple AJAX approach that includes an HTML page client and a JavaScript making an AJAX call and receiving a response. A remote page will receive the parameter information and respond to the JavaScript call method with the necessary information. This concept will provide a real time user interface for an order processing screen or a stock evaluation screen based on the fundamental and technical analysis of the stock. I have used this to evaluate a list of my stocks, which is the basic idea of this article. Let's say, you have a list of order items and all the order items have to be processed at the same time. Generally, users want to see a progress/status for each order item as they start processing, and you can design your application so that the user will see an indicator next to the current order item as it starts the process. Once the order process request is completed, a successful completion image is shown next to the order time, as shown in the following diagrams:

Diagram-1: Showing the list of order items

ajax_net_process1.jpg

Diagram-2: Once the Submit Order button is clicked, the page starts it process without a postback, and you can see the status of the process as shown above.

ajax_net_process2.jpg

Diagram-3: You can change the visual presentation. I am showing the wrong image as the Failed status for the order item framework.

ajax_net_process3.jpg

Implementation and Using the Code

To keep it simple, I will walk through the code and the necessary HTML pages. I have separated all the AJAX based JavaScript methods and functions to a .js file and included that file as the source in the HTML pages.

I have separated the GetXmlHttpObject and implemented it on the HTML page, just to keep it simple for the reader. Following are the steps to implement:

  1. Create a “default.aspx” using Visual Studio (I have used Visual Studio 2005, but you can try this using any version, or even an HTML/text editor should be fine).
  2. Create a second page called “startproc.aspx”. (A remote page that will receive the request from the AJAX call from the JavaScript, and will respond with the necessary information.)
  3. Create a third page called “endproc.aspx”. (You can skip this page completely and make use of the “startproc.aspx” with a different implementation, but to keep it simple, I have used this page.)
  4. Create a .js file and name it “ajaxsrc.js”.
  5. Create an Images directory and add three images.

Default.aspx

This is the only page that is used as the user interface. The AJAX callback method will request information from the remote page by passing the order item information, and changes the state immediately once it receives information back from the remote page, without making any postback calls.

Step 1: Add the following code to “default.aspx”:

HTML
<script language="javascript" 
   type="text/javascript" src="ajaxsrc.js">
</script>
<script language="javascript" type="text/javascript"> 

var xmlHttp;
function submitOrder(){
    if(navigator.appName == 'Netscape'){
      alert('I have only tried to work with IE browser, but i am sure' + 
            ' you can figure it out to make it work for other then ' + 
            'IE browser.\n You can use XmlHttpRequest object to support ' + 
            'cross-browser.\nHope this helps.');
    }//if
    else{
        //script for running IE
        ProcessOrder('startproc.aspx');
    }//else 
}//submitOrder() 

//***************************************************************
//*** Ajax Implementation
//***************************************************************
function ProcessOrder(pagename){
    var url = null;
    url = pagename;
    document.getElementById('btnOrder').disabled = true;
    try{
        xmlHttp = GetXmlHttpObject(CallbackMethod); 
        SendXmlHttpRequest(xmlHttp, url);
    }//try
catch(e){} 
}//end: functino getAccess 

function getOrder(item){
    //This is used only to keep as simple as possible and declartion/scope of 
    //the variables can be implemented in many different ways. 
    //I have used for the purpose of understanding the model.

    var iimg = item.indexOf(':'); 
    var sitem = item.substring(iimg+1, item.length);
    try{ 
        if(item != 'done'){
            if(item.search(/Img:/) == 0){ 
                var img1 = document.getElementById('Img' + sitem);
                img1.src = document.getElementById('ImgProc').src;
                img1.style.visibility = 'visible'; 
                ProcessOrder('endproc.aspx');
            }//if img
        else if(item.search(/tick:/) == 0){
            //Successful 
            var r_img = document.getElementById('ImgR').src; 
            var img1 = document.getElementById('Img' + sitem); 
            img1.src = r_img;
            img1.style.visibility = 'visible'; 
            ProcessOrder('startproc.aspx'); 
        }//else if ticker : right
        else if(item.search(/error:/) == 0){ 
            //Failed 
            var w_img = document.getElementById('ImgW').src;
            var img = document.getElementById('Img' + sitem); 
            img.src = w_img;
            img.style.visibility = 'visible';
            ProcessOrder('startproc.aspx'); 
        }//else if Wrong
        else if(item == 'syerror:'){
            alert('System Error Occured'); 
            return;
        }//else if
        else
            alert('unknown Error');
            return; 
        }//if
        else{
            alert('Request Processed Successfully.');
            document.getElementById('btnOrder').disabled = false;
            return;
        }//else
    }//try
    catch(e){}//catch
}//getPage(url); 

//CallbackMethod will fire on state Change
function CallbackMethod(){ 
    try{
        //readyState of 4 or 'complete' 
        //represents that data has been returned 
        if (xmlHttp.readyState == 4 || xmlHttp.readyState == 'complete'){ 
            var response = xmlHttp.responseText; 
            if (response.length > 0){ 
                getOrder(response);
            }//if
        }//if
    }//try
catch(e){}
}//end CallbackMethod
//*******************************************************************************
</script>

//head
//Body
<form id="form1" runat="server">
<div>
<table style="position: relative;" border="0" 
        cellpadding="1" cellspacing="1">
<tr>
<td colspan="2">Process my Order:</td>
</tr>
<tr>
<td style="width:10px;"></td>
<td valign="top">
<asp:Panel ID="pnlOrder" runat="server" 
         Style="position: relative"></asp:Panel>
</td>
</tr>
<tr>
<td></td>
<td><input type="button" value="Submit Order" 
      id="btnOrder" name="btnOrder" 
      onclick="submitOrder();"/></td>
</tr>
</table> 
</div>
<asp:Image ID="ImgW" runat="server" ImageUrl="images/wrong.gif" 
Style="position: relative;visibility:hidden;" />
<asp:Image ID="ImgR" runat="server" ImageUrl="images/right.gif" 
Style="position: relative;visibility:hidden;"/>
<asp:Image ID="ImgProc" runat="server" ImageUrl="images/indicator.gif" 
Style="position: relative;visibility:hidden;"/>
</form>

Default.aspx.cs

The code-behind file is used to generate the order collection and display it on the screen. I have used the code-behind to frame the order grid using the HTML table and a static order collection. You can access a database or a file object and populate the DataTable to replace the static order collection.

Step 2: Add the following code to “default.aspx.cs”.

C#
public partial class _Default : System.Web.UI.Page {

    protected void Page_Load(object sender, EventArgs e){
        fillSecurityOrder();
    }//page_Load

    protected void fillSecurityOrder(){
        //There are numerous ways to deal with the styles. I tried here one of a 
        //different approach to not to add a styles.css.
        //I do not encourage to use this approach of using the styles as i believe 
        //this will have a maintenance overhead.
        string headerStyle = "font-weight:bold;background-color:" + 
                             " #99CCFF;font-family:Trebuchet MS;";
        string rowevenStyle = "background-color: #FBEFE1;" + 
                              "font-family:Trebuchet MS;font-size: 9pt;";
        string rowoddStyle = "background-color: #F9FDDD;" + 
                             "font-family:Trebuchet MS;font-size: 9pt;";        

        //set up the table Properties.
        //You could add styles as per your standards.
        Table theTable = new Table();
        theTable.BorderStyle = BorderStyle.Solid;
        theTable.BorderWidth = 1;
        theTable.CellPadding = 1;
        theTable.CellSpacing = 1;
        theTable.Width = Unit.Pixel(220);        

        //Setup the Table Header details.
        TableRow theRow = new TableRow();
        TableCell theCell1 = new TableCell();
        TableCell theCell2 = new TableCell();
        theCell1.Text = "Order Item";
        theCell1.Style.Value = headerStyle;
        theCell2.Text = "Status";
        theCell2.Style.Value = headerStyle;
        theRow.Controls.Add(theCell1);
        theRow.Controls.Add(theCell2);
        theTable.Rows.Add(theRow);

        //Set up the body details.
        //To keep it simple i am using a static elements.
        //But you can create a dynamic element collection 
        //from a file or a datastore.
        DataTable tblOrderCollection = OrderCollection();
        int iStyle = 0;
        string applyStyle = string.Empty;
        foreach (DataRow dr in tblOrderCollection.Rows){

            //diff way to apply alternate styles.
            iStyle++;            
            int imod = iStyle % 2;
            if (imod == 1) applyStyle = rowoddStyle;
            else applyStyle = rowevenStyle;
            
            TableRow Row = new TableRow();
            TableCell CellOne = new TableCell();
            TableCell CellTwo = new TableCell();
            CellOne.Controls.Add(createItem(dr["sOrderItem"].ToString()));
            CellOne.Style.Value = applyStyle;
            CellOne.Height = 20;
            CellTwo.Controls.Add(createImage(dr["sOrderItem"].ToString()));
            CellTwo.Style.Value = applyStyle;
            CellTwo.Height = 20;
            Row.Controls.Add(CellOne);
            Row.Controls.Add(CellTwo);

            theTable.Controls.Add(Row);
        }//foreach

        //In order to track the OrderItem on the receiving end 
        //via the callback method and provide
        //a realtime analysis of the process status i have used 
        //a Session objects that can share the
        //information between the Ajax and the CallbackMethod.
        Session["OrderBucket"] = tblOrderCollection;
        pnlOrder.Controls.Add(theTable);

    }//fillSecurityOrder()

    //Refactor the Item
    protected Label createItem(string Ticker){
        Label theLabel = new Label();
        //Caution is needed so that 
        //you can always create a unique itemID
        theLabel.ID = Ticker;        
        theLabel.Text = Ticker;

        //return the Label
        return theLabel;
    }//createItem


    //Refactor the Item
    protected Image createImage(string Ticker){
        Image theImage = new Image();
        //Caution is needed so that You can always create a unique itemID        
        theImage.ID = "img" + Ticker;
        theImage.ImageUrl = "images/indicator.gif";
        theImage.Style["visibility"] = "hidden";

        //return the Image
        return theImage;
    }//createImage


    //get OrderCollection
    protected DataTable OrderCollection(){
        //For prototype only. The Order Collection can be created 
        //and filled dynamic based on your
        //database connection or a file system object.
        DataTable theDataTable = new DataTable();        
        theDataTable.Columns.Add(new DataColumn("sOrderItem", typeof(string)));
        theDataTable.Columns.Add(new DataColumn("isProc", typeof(Int32)));

        //Create Row One
        DataRow drOne;
        drOne = theDataTable.NewRow();
        drOne["sOrderItem"] = ".NET";
        drOne["isProc"] = 0;
        theDataTable.Rows.Add(drOne);

        //Create Row Two
        DataRow drTwo;
        drTwo = theDataTable.NewRow();
        drTwo["sOrderItem"] = "Ajax";
        drTwo["isProc"] = 0;
        theDataTable.Rows.Add(drTwo);

        //Create Row Three
        DataRow drThree;
        drThree = theDataTable.NewRow();
        drThree["sOrderItem"] = "Framework";
        drThree["isProc"] = 0;
        theDataTable.Rows.Add(drThree);

        //Create Row Four
        DataRow drFour;
        drFour = theDataTable.NewRow();
        drFour["sOrderItem"] = "Code Project";
        drFour["isProc"] = 0;
        theDataTable.Rows.Add(drFour);

        //return the OrderCollection
        return theDataTable;
    }//OrderCollection()

}//class

Startproc.aspx

This page is used to receive the current order item and notify the AJAX callback method that the order item is received and that the user can be notified. You can change an image/icon, or a message, or a status symbol to let the user feel that the current order item has now started processing. When you add this page to your project, do not make any modifications or changes to the HTML design as this is a remote page and the user interface is not handled in any client object in this project.

Step 3: Add the following to “startproc.aspx.cs”:

C#
public partial class startproc : System.Web.UI.Page{

    protected void Page_Load(object sender, EventArgs e){
        //Clear the Cache.
        Response.Cache.SetCacheability(HttpCacheability.NoCache);
        Response.Clear();

        //Check if the Session object exits.
        if (Session["OrderBucket"] != null){
            //get the Session Order Collection to a DataTable.
            DataTable theTable = (DataTable)Session["OrderBucket"];
            //Find or get details of each Order from the Order Collection.
            foreach (DataRow dr in theTable.Rows){
                int iproc = Convert.ToInt32(dr["isProc"].ToString());
                if (iproc == 0){
                    //This is for understanding purpose only. I am using an 
                    //int to change the process status
                    //so the UI can represent the current state as 
                    //processed successfully or failed.
                    dr["isProc"] = 2;//set up 2 as this processed.
                    theTable.GetChanges();
                    
                    //Update the Session Order Collection so that the callback 
                    //method will move to next Order Item that is in the queue.
                    Session["OrderBucket"] = theTable;                    
                    Response.Write("Img:" + dr["sOrderItem"].ToString());
                    Response.End();
                }//if   

            }//foreach

            //Respond when all the Order Items have been processed.
            Response.Write("done");
            Response.End();            
        }//if

    }//Page_Load

}//class

Endproc.aspx

This page is used to receive the current order item information stating that the order item shall be processed, and to respond to the AJAX callback method on the default.aspx page. This page is separated from startproc.aspx only to show how a start and end process is handled within the process. When you add this page to your project, do not make any modifications or changes to the HTML design as this is a remote page and the user interface is not handled in any client object in this project.

Step 4: Add the following code to “endproc.aspx.cs”:

C#
public partial class endproc : System.Web.UI.Page{
    protected void Page_Load(object sender, EventArgs e){
        //Clear the Cache
        Response.Cache.SetCacheability(HttpCacheability.NoCache);
        Response.Clear();

        //Check if the Session object exists.
        if (Session["OrderBucket"] != null){
            //get the Session Order Collection to a DataTable.
            DataTable theTable = (DataTable)Session["OrderBucket"];
            //Find or get details of each Order from the Order Collection.
            foreach (DataRow dr in theTable.Rows){
                int iproc = Convert.ToInt32(dr["isProc"].ToString());
                if (iproc == 2){
                    //Setting to a different process, for understanding purpose only.
                    //In the overall process for a given Order Item you can set up different
                    //process stages as the order Item is passed to different processes
                    dr["isProc"] = 1;
                    theTable.GetChanges();
                    //*********************************************/
                    //Actual process will be handled here, such as calling an API, 
                    //Webservice, writing to a differnt system,
                    //synchronize with other object etc.
                    //I am showing a thread to sleep for 2000 milli sec. to 
                    //simulate as if it is processed.
                    System.Threading.Thread.Sleep(2000);
                    //*********************************************/

                    //Update the Session Order Collection so that the callback method 
                    //will move to next Order Item that is in the queue.
                    Session["OrderBucket"] = theTable;
                    //Iam trying to simulate a process that
                    // has problem and would report on the UI
                    //i.e. i have taken an example "framwork" 
                    //as some problems in processing the order.
                    if (string.Compare(dr["sOrderItem"].ToString(), "Framework") == 0)
                        Response.Write("error:" + dr["sOrderItem"].ToString());
                    else
                        Response.Write("tick:" + dr["sOrderItem"].ToString());

                    Response.End();
                }//if   

            }//foreach

            //Respond when all the Order Items have been processed.
            Response.Write("done");            
            Response.End();
            //theDCFAgent.CallAsyncServices();
        }//if
    }//PageLoad

}//class

Ajaxsrc.Js

This page is created to keep all the JavaScript related functions and methods at one place. You can also include the AJAX specific implementation in this file, but to keep it simple, I tried to separate and use it on the HTML page, so that if you want to call multiple AJAX callback methods, then you can if you separate the AJAX implementation to the HTML page. In short, I treated this Js file as the AJAX framework.

Step 5: Copy the following code to “ajaxsrc.js”:

JavaScript
// JScript File
function GetXmlHttpObject(handler)
{ 
   try{
        var objXmlHttp = null;                
        objXmlHttp = GetMSXmlHttp();
        if (objXmlHttp){
            objXmlHttp.onreadystatechange = handler;
         }                               
         return objXmlHttp;
      }
   catch(e){}            

} 
function GetMSXmlHttp()
{
    //alert('getMsXmlHttp');
    var xmlHttp = null;
    var clsids = ["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.5.0",
                 "Msxml2.XMLHTTP.4.0","Msxml2.XMLHTTP.3.0", 
                 "Msxml2.XMLHTTP.2.6","Microsoft.XMLHTTP.1.0", 
                 "Microsoft.XMLHTTP.1","Microsoft.XMLHTTP"];
    for(var i=0; i<clsids.length && xmlHttp == null; i++) {
        xmlHttp = CreateXmlHttp(clsids[i]);
    }
    return xmlHttp;
}
function CreateXmlHttp(clsid) {
    //alert('CreateXmlHttp');
    var xmlHttp = null;
    try {
        xmlHttp = new ActiveXObject(clsid);
        lastclsid = clsid;
        return xmlHttp;
    }
    catch(e) {}
}

function GetMSXmlHttp() {
    //alert('GetMSXmlHttp');
    var xmlHttp = null;
    var clsids = ["Msxml2.XMLHTTP.6.0",
                  "Msxml2.XMLHTTP.4.0",
                  "Msxml2.XMLHTTP.3.0"];
    for(var i=0; i<clsids.length && xmlHttp == null; i++) {
        xmlHttp = CreateXmlHttp(clsids[i]);
    }
    return xmlHttp;
}

function SendXmlHttpRequest(xmlhttp, url){ 
    xmlhttp.open('POST', url, true); 
    xmlhttp.send(null); 
}//end SendXmlHttpRequest

Step 6: Create a sub-directory or an images folder, and add the following three images:

Image Name

Image ID

Description

Image1 ajax_net_process5.gif

indicator.gif

This image is used to show the user that the process has been started. You can create your own image that looks like a Please Wait icon.

Image2 ajax_net_process6.gif

Used to show the user that the process completed successfully for the order item.

Image3 ajax_net_process7.gif

wrong.gif

Used to show the user that the process failed to complete successfully for the order item.

Finally, after all the above steps, you will see the project/solution as follows:

ajax_net_process4.jpg

Conclusion

As we all know, there are many different best practices to implement the code for a given configuration/setting level. I am not discussing a particular implementation here, but would like to share a concept of using these technologies/concepts and implementing the solution. The above model was tested on IE 5.5 and higher.

Happy coding…

License

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