Displaying Progress During Long-Running ASP.NET Scripts

Sometimes it is necessary for a webpage to process large data or files on the server whilst updating the page with a progress indicator. It is always good practice to keep the user informed on the current status, but this is especially true when dealing with potentially long-running tasks. Here I will explain how this can be achieved on an ASP.NET website using some simple JavaScript.

The page that starts the process and displays the output should contain a button and span, as shown below. The button triggers the process while the span contains the output.

<input id="btnTrigger" type="submit" value="Start" onclick="BeginProcess(); return false;" />
<span id="lblOutput"></span>

The button calls the JavaScript function BeginProcess(), which creates an invisible iframe and sets its source to be the page containing the process. It is added to the DOM immediately before the span. Finally, the button is disabled to stop repeated clicking.

function BeginProcess() {
    // Create an iframe
    var iframe = document.createElement("iframe");

    // Make the iframe invisible
    iframe.style.display = "none";

    // Point the iframe to the location of the process
    iframe.src = "Process.aspx?Framed=True";

    // Add the iframe to the DOM. The process will begin execution at this point
    var lblOutput = document.getElementById("lblOutput");
    lblOutput.parentNode.insertBefore(iframe, lblOutput);

    // Disable the button
    document.getElementById('btnTrigger').disabled = true;
};

In this case Process.aspx is the example long-running process. It is nothing but a Thread.Sleep(), but this can be changed to suit different requirements. It is in Page_Load() so that it is run as soon as the page is loaded.

protected void Page_Load(object sender, EventArgs e)
{
    if (Request.QueryString["Framed"] != "True")
    {
        // If user has navigated to page manually, redirect
        Response.Redirect("~/Default.aspx");
    }
    else
    {
        // Show progress
        UpdateProgress("Starting");

        // Simulate long-running process
        int rowCount = 1000;
        for (int currentRow = 0; currentRow <= rowCount; currentRow++)
        {
            Thread.Sleep(15);

            // Show progress
            UpdateProgress("Processing " + Math.Round(((double)currentRow / (double)rowCount) * 100) + "%");
        }

        // Show progress
        UpdateProgress("Complete");
    }
}

Firstly, the query string is verified to ensure that the page cannot be accessed directly by checking that ‘Framed’ is true. All update messages are set by calling UpdateProcess(), including the initial and end ones. In each iteration of the loop is another call to this method, with a percentage passed as the parameter.

private void UpdateProgress(string output)
{
    try
    {
        // Write out the parent script callback
        Response.Write(string.Format("<script type=\"text/javascript\">parent.UpdateProgress('{0}');</script>", output));

        // To be sure the response isn't buffered on the server
        Response.Flush();
    }
    catch (HttpException)
    {
        //throw new Exception("HTTP exception");
    }
    catch (Exception)
    {
        //throw new Exception("Other exception");
    }
}

This method writes a line in the response. Rather than outputting the text directly, the JavaScript function UpdateProgress() is called on the iframes parent (the page with the ‘Start’ button), with the output text as a parameter. This ensures that only the most recent message is displayed to the user. Exceptions should be handled appropriately.

function UpdateProgress(message) {
    document.getElementById('lblOutput').innerHTML = message;
};

This function replaces the HTML contents of the span with the most recent message from the back-end.

Source code for Long-Running Process example.

«
»