Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

User Feedback For Long Running Tasks in ASP.NET

0.00/5 (No votes)
27 Apr 2012 1  
Giving a web page user regular indication of the progress of a server task

Introduction

The simple code snippets presented here are a solution to the problem of how to provide regular feedback to a user of an ASP.Net page whilst they are waiting for a long running task to complete on the server.

Background

This solution came about, as is often the case, in response to late changes in the requirements for a newly developed system. The system originally was a simple intranet page to display a report, with options to filter and sort the data on the report.

The late change was that the users requested the ability to save the underlying data as a series of xml documents on the file system, for offline use. Clearly if this had been known at the outset then the architecture of the solution may have been designed differently.

The problem became one of allowing the user to see that something was actually happening on the server, in a process that could take many minutes to complete. Options considered included just displaying progress bar, or hourglass, or firing off a the task asynchronously and expecting the user to refresh the page for updates. Niether of these were deemed totally satisfactory.

As a server task is unable to push status updates to the client (browser), the solution is to get the browser to request regular updates from the server.

In a nutshell, the solution chosen involves firing off the long running task asynchronously, and monitoring it on a separate thread initiated by browser requests.

In practice, the long running task writes progress updates to a shared area, and the web page makes regular requests to the server to read that shared area and display the progress to the user.

Using the code

A simplified version of the solution is shown below, in order to demonstrate the principles.

In summary, we have a web page, Default.aspx, on which there is a button which triggers the long running server task. The task is deployed server-side as a generic handler, ProcessTask.ashx.

Initiation of this handler takes place in Javascript fired when the button is clicked:

       var tmrProgress;
       
        //SENDING XML
        function postSend() {
            var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");

          divResponse.innerHTML = "";

            // 'true' specifies that it's a async call
            xmlhttp.Open("POST", "ProcessTask.ashx", true);

            // Register a callback for the async, call and display a completion message
            xmlhttp.onreadystatechange = 
                 function () {
                    if (xmlhttp.readyState == 4) {
                        window.clearInterval(tmrProgress);
                        var response = xmlhttp.responseText;
                        divResponse.innerHTML += response;
                       
                }
        }

         // Send the actual request
        xmlhttp.Send();
        tmrProgress = window.setInterval("showSendProgress();", 500);

        }

The Javascript fires off an asynchronous request to the handler, registers a callback for completion of the task, and initiates a timer which will request progress updates fromt the server.

For test purposes, the server task is simulating time taken for creation of files by means of a thread.sleep command. On "submission" of each file, a count is incremented and saved to the shared area, in this case the .NET cache is used.

Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest

        Const NumberOfFilesToSend As Integer = 50

        context.Response.ContentType = "text/plain"

        Try

            For i As Integer = 1 To NumberOfFilesToSend

                'report the current status of the task to shared cache area
                context.Cache("FileSending") = i

                'simulate a time intensive task
                Threading.Thread.Sleep(100)
            Next

            context.Response.Write(String.Format("Finished sending {0} files", NumberOfFilesToSend))

        Catch ex As Exception
            context.Response.Write(String.Format("Error sending files: {0}", ex.Message))
        End Try

    End Sub

For progress reporting, the function showSendProgress() is called on the browser at 500ms intervals, triggered by the timer tmrProgress. This function calls a separate generic handler to get the current status from the server:

  function showSendProgress() {
            var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");

            // 'true' specifies that it's a async call
            xmlhttp.Open("POST", "GetTaskProgress.ashx", true);

            // Register a callback for the call
            xmlhttp.onreadystatechange =
                function () {
                if (xmlhttp.readyState == 4) {
                        var response = xmlhttp.responseText;
                        divResponse.innerHTML = "Processing File:  " + response;
                
                }
                }
            // Send the actual request
            xmlhttp.Send();
        }

Responses from handler GetTaskProgress.ashx are captured in the callback, and update the status on the page, in divResponse.

GetTaskProgress.ashx simply queries the cache and reports back to the client

   Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest

        context.Response.ContentType = "text/plain"

        If Not HttpContext.Current.Cache("FileSending") Is Nothing Then
            context.Response.Write(HttpContext.Current.Cache("FileSending"))
        End If

    End Sub

Points of Interest

One point with the solution presented is that it is not accurately reporting on the exact status of the server task in real-time, it's just giving a regular indiction to the user that something is progressing. Some fine tuning of the refresh rate would be necessary depending of the duration of the task, as well as the frequency it updates it's own status.

.Net cache has been used as a simple shared area for simplicity's sake, but this could easily be changes to use session state, SQL server or any other shared area.

History

Version 1.0 27 April 2012

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here