Easy incremental status updates for long requests
AJAX, ASP.NET, JavaScript, UI By Dave Ward on October 3rd, 2007
A problem that has always plagued web developers has been providing detailed progress indication for server-side tasks. The stateless nature of the HTTP protocol makes implementing a mechanism for constant, stateful progress information cumbersome. The main problem is that a given group of server side tasks will generally only result in one, aggregate response from the server.
In the ASP.NET community, several solutions have been offered. Some even provide an entire framework for monitoring the status of tasks in progress. However, they are convoluted and depend on constant server polling. I find polling for progress indication to be an inefficient approximation, when exact data is readily available.
With that in mind, I’d like to share an alternate method that I’ve used in the past with great success.
Note: While this example is written in ASP.NET and C#, the technique I’m going to describe is very easily portable to any language and platform.
Worse than watching paint dry
Suppose you had a long running group of tasks, such as these:
public void PrepareReport() { // Initialization. System.Threading.Thread.Sleep(10000); // Gather data. System.Threading.Thread.Sleep(6000); // Process data. System.Threading.Thread.Sleep(20000); // Clean up. System.Threading.Thread.Sleep(4000); }
That’s 40 seconds we’re hoping the user will patiently wait on some sign of life from the server. Assuming that something went wrong, users are likely to give up and navigate away during something that runs this long.
Constant, granular progress feedback is exactly what’s needed to keep the user engaged while the tasks complete.
Asynchronously executing the tasks
The first thing we need to do is get away from the HTTP request/response cycle, by using some sort of asynchronous execution model. By doing that, the browser is still available to update the UI throughout the process execution. Rather than use formal AJAX, I chose to do something even easier.
Iframes: They’re underused, given the power and flexibility they provide, but not today. In this scenario, using an iframe is a great way to asynchronously execute the long running process, without being dependent on any particular AJAX framework and without tying up an XMLHttpRequest.
<input type="submit" value="Start Long Running Process" id="trigger" onclick="BeginProcess(); return false;" />
<script type="text/javascript"> function BeginProcess() { // Create an iframe. var iframe = document.createElement("iframe"); // Point the iframe to the location of // the long running process. iframe.src = "LongRunningProcess.aspx"; // Make the iframe invisible. iframe.style.display = "none"; // Add the iframe to the DOM. The process // will begin execution at this point. document.body.appendChild(iframe); } </script>
When the button is clicked, an invisible iframe is dynamically generated and used to execute LongRunningProcess.aspx in the background. Next, let’s take a look at exactly what goes on when LongRunningProcess.aspx is executed.
Streaming update information during the process
In LongRunningProcess.aspx.cs’s Page_Load, I’m going to include the same set of tasks as before, but I’m also going to stream constant updates about what stage of the process is executing.
protected void Page_Load(object sender, EventArgs e) { // Padding to circumvent IE's buffer* Response.Write(new string('*', 256)); Response.Flush(); // Initialization UpdateProgress(0, "Initializing task."); System.Threading.Thread.Sleep(10000); // Gather data. UpdateProgress(25, "Gathering data."); System.Threading.Thread.Sleep(6000); // Process data. UpdateProgress(40, "Processing data."); System.Threading.Thread.Sleep(20000); // Clean up. UpdateProgress(90, "Cleaning up."); System.Threading.Thread.Sleep(4000); // Task completed. UpdateProgress(100, "Task completed!"); } protected void UpdateProgress(int PercentComplete, string Message) { // Write out the parent script callback. Response.Write(String.Format( "<script>parent.UpdateProgress({0}, '{1}');</script>", PercentComplete, Message)); // To be sure the response isn't buffered on the server. Response.Flush(); }
For each step of the process, this outputs a JavaScript callback to the page that opened the iframe. Using the combination of Response.Write and Response.Flush ensures that the callbacks are emitted in real-time, as the tasks are completed.
Since the relative run time of my example tasks are known in advance, I included completion percentages as well as the status messages. The raw output will look something like this:
<script>parent.UpdateProgress(0, 'Initializing task.');</script> <script>parent.UpdateProgress(25, 'Gathering data.');</script> <script>parent.UpdateProgress(40, 'Processing data.');</script> <script>parent.UpdateProgress(90, 'Cleaning up.');</script> <script>parent.UpdateProgress(100, 'Task completed!');</script>
*Note: Internet Explorer buffers the first 256 bytes of any response. To circumvent that behavior, it’s important that you push 256 characters down the response before anything else. Otherwise, the first few status updates will be buffered and the callbacks won’t be executed at the correct time (or at all, visibly) for users running IE.
Handling the callbacks and displaying status updates
The final step is to create a client side function to handle the callbacks coming from the iframe. To keep things simple, we’ll use the submit button’s text value to display the status updates.
function UpdateProgress(PercentComplete, Message) { document.getElementById('trigger').value = PercentComplete + '%: ' + Message; }
Now, the submit button’s text will continuously update throughout the process, to reflect the current status of the process in real-time.
We’ve only just begun (and by we, I mean you)
I purposely kept the presentation very simple in this example, so that the underlying mechanism isn’t obscured by DHTML code.
However, if updating the button’s text isn’t visually stimulating enough for you, it’s easy to dress this technique up any way you want. For example, if you return percentages in your implementation, jsProgressBarHandler would be trivial to integrate with this technique and provides an excellent visualization.
You could do something similar to this, without an iframe, by using a sequence of page method or web service calls that had progress updates in between. The problem is that you’d have to move a lot of your logic into the presentation layer to do that. I would strongly advise against going down that road, when avoidable.
In real world use, you’re probably going to want to specify parameters to the long running process. Loading it in the Session is one way do to that. Even better, if you can manage it, is to provide arguments on the QueryString when setting the iframe’s src attribute. Then, you can check those parameters in LongRunningProcess.aspx.cs and respond accordingly.
As always, taking the source code for a spin is the best way to get a feel for it. Let me know what you think.
Similar posts
What do you think? Your comments are welcomed.
I appreciate all of your comments, questions, and other feedback, but please try to stay on topic. If you have a question unrelated to this post, I recommend posting on the ASP.NET forums. If you post there and then contact me with a link to the post, I'll try to take a look at it for you.
If you're replying to an existing comment, please use the threading feature. To do this, click the "Reply to this comment" link underneath the comment you're replying to.

Comments
Awesome.. Thanks a lot. :)
Great post! It’s nice to see that I’m not the only one who has used iframes to simulate ajax. (Or is it still ajax? Who knows anymore.) =)
Thanks, Ira.
I’m right there with you on the iframes. I’d been using them for remote script calls quite a while when the AJAX craze hit.
If only I’d thought of putting an arbitrary label on it back then!
I like the approach, but I don’t understand how the content is actually returned back. For example, say LongRunningProcess.aspx is dynamically generating a PDF. If we are sending back HTML/Javascript periodically to update the status, but then later return the binary PDF content in the same response, how is the browser going to display the PDF? It will assume that the binary content is still “text/html” and thus will just display a bunch of gobblygook in the browser instead of displaying the PDF in the Acrobat plug-in.
I’m probably missing something, so if you could please elaborate on that part, that’d be great!
The final presentation method will vary depending on what sort of data is being returned and how it needs to be displayed.
In the PDF scenario you describe, here are a couple presentation ideas:
If you wanted a PDF download at completion, you could emit a document.href JavaScript block after the 100% progress update. Using that, you could redirect the iframe to a download script that served the PDF up with the correct binary response type. Similar to this iframe download method.
If you wanted to display the PDF in the calling window, you could use parent.document.href to redirect it to a PDF file you had saved during the generation step.
Hope that helps. I may write a more specific post in the future, that combines this progress indication with the iframe download technique, if there’s enough interest in it.
I tried combining the progress indication and the iframe download techique, and it works! The problem is now I get the IE info bar message “IE blocked this site from downloading files…”. If I click the info bar and select Download File, I have to the generate the file again (bummer).
My implementation details: I modified UpdateProgress to call a new function GetReport when we reach 100%. GetReport creates a hidden iframe and sets the iframe.src to a page that sends the report to the browser as a download. (Is that what you meant by issuing a document.href?)
I’d be interested if someone has a work around for the info bar message. If not, I’ll probably resort to displaying a download link on the page after the long process is complete (an extra step for the user.) I plan to hold the generated report temporarily in a session variable.
BTW, Dave, I’m really glad to have found your site. Wonderful information here!
Thanks for the compliment. I’m glad you’ve found the site useful.
If you can, save the file to the file system during generation. Then, after outputting your last parent.UpdateProgress(), output a document.href = ‘filename’.
That will point the already existing iframe to the file and should result in a regular file download prompt.
I would love to see how this could be implemented to track file upload progress.
That’s something I do intend to explore at some point, but don’t have a solid solution for today.
Tracking file upload progress has been an issue for developers for some time now. All I know is to make it ajax(ish) your upload box must be in an iframe. As far as attaching a callback to the upload process, I haven’t quite figured that one out.
The problem is that ASP.NET caches the entire file upload in memory until you do something with it server-side. So, without using a custom HttpHandler, there’s no way to get access to incremental progress information.
could you possibly suggest a way i could use this while my server is creating a data table ? the table takes time and i would like it to send a response to the webpage each time a new row is added to the table. this table is being created on the default.aspx.cs and not the LongRunningProcess.aspx.cs file.
thanks.
You can’t use this technique that way. The separation of your main page and the iframe page is crucial to being able to provide status updates.
document.body.appendChild(iFrame); throws a JScript error. I’m unable to run this code :(
JavaScript is case sensitive. iFrame isn’t the same thing as iframe.
I downloaded the code and ran Default.aspx. It causes the button to blur and disables it in the browser. However, the code in LongRunningProcess.aspx never gets called :(
You might want to check or delete the web.config. I believe it was 3.5 targeted, which caused some trouble for some people.
Hi Dave,
I liked this hack (if i may) alot… Is there anyway I could reproduce something similar in an update panel where response.flush() will not work?
Thanks,
Eugene.
Unfortunately, no. That’s more or less the problem that this technique aims to solve.
Great post i like it :)
I downloaded your sample and I encountered a problem where all the scripts injected into the iframe fire at the end of the processing and not after the 0, 25, 40, 90, and 100 % events when using IE6.0. Using Firefox seems to be ok.
This is a great article and i hope to see it working.
If you call the LongRunningProcess.aspx page directly, does it progressively output its callbacks? Make sure you left the 256 bytes of padding in the beginning. IE buffers the first 256 bytes of a response.
Dave the cause seems to have been Fiddler, which was running in the background. Thanks for the help.
Thanks for the follow up. That’s good to know about Fiddler.