Reporting server side operation progress with jQuery UI Progressbar

A colleague of mine recently had a problem with using jQuery UI Progressbar for reporting progress of asynchronous server side operations initialized via AJAX request. I decided to create a short sample for him and everybody else who might need it. I'm going to show how to do it in ASP.NET MVC and ASP.NET WebForms.
We will start with ASP.NET MVC sample. First thing to do is adding some JavaScript and CSS references:
<link href="<%= Url.Content("~/Content/jquery-ui-1.8.2.css") %>" rel="stylesheet" type="text/css" />
<script src="<%= Url.Content("~/Scripts/jquery-1.4.2.min.js") %>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/jquery-ui-1.8.2.min.js") %>" type="text/javascript"></script>
Markup for Progressbar is very simple (just one div), so we will need only two lines of HTML in this sample:
<button id="operation">Operation</button>
<div id="progressbar" style="width:500px"></div>
Now some interesting stuff begins. We need two controller actions. One for triggering actual process and one for reporting progress:
/// <summary>
/// Action for triggering long running operation
/// </summary>
/// <returns></returns>
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Operation()
{
HttpSessionStateBase session = Session;

//Separate thread for long running operation
ThreadPool.QueueUserWorkItem(delegate
{
int operationProgress;
for (operationProgress = 0; operationProgress <= 100; operationProgress = operationProgress + 2)
{
session["OPERATION_PROGRESS"] = operationProgress;
Thread.Sleep(1000);
}
});

return Json(new { progress = 0 });
}

/// <summary>
/// Action for reporting operation progress
/// </summary>
/// <returns></returns>
[NoCache]
public ActionResult OperationProgress()
{
int operationProgress = 0;

if (Session["OPERATION_PROGRESS"] != null)
operationProgress = (int)Session["OPERATION_PROGRESS"];

return Json(new { progress = operationProgress }, JsonRequestBehavior.AllowGet);
}
What is missing is JavaScript, which will initialize Progressbar, trigger the operation and set timer for updating progress:
<script type="text/javascript">
$(document).ready(function () {
//Progressbar initialization
$("#progressbar").progressbar({ value: 0 });
//Button click event
$("#operation").click(function (e) {
//Disabling button
$("#operation").attr('disabled', 'disabled');
//Making sure that progress indicate 0
$("#progressbar").progressbar('value', 0);
//Perform POST for triggering long running operation
$.post('<%: Url.Action("Operation") %>', function (data) {
//Updating progress
$("#progressbar").progressbar('value', data.progress);
//Setting the timer
window.progressIntervalId = window.setInterval(function () {
//Getting current operation progress
$.get('<%: Url.Action("OperationProgress") %>', function (data) {
//Updating progress
$("#progressbar").progressbar('value', data.progress);
//If operation is complete
if (data.progress == 100) {
//Clear timer
window.clearInterval(window.progressIntervalId);
//Enable button
$("#operation").attr('disabled', '');
}
});
}, 500);
});
});
});
</script>
The result looks like this:
To make it work in ASP.NET WebForms, we need two PageMethods instead of actions:
/// <summary>
/// PageMethod for triggering long running operation
/// </summary>
/// <returns></returns>
[System.Web.Services.WebMethod(EnableSession=true)]
public static object Operation()
{
HttpSessionState session = HttpContext.Current.Session;

//Separate thread for long running operation
ThreadPool.QueueUserWorkItem(delegate
{
int operationProgress;
for (operationProgress = 0; operationProgress <= 100; operationProgress = operationProgress + 2)
{
session["OPERATION_PROGRESS"] = operationProgress;
Thread.Sleep(1000);
}
});

return new { progress = 0 };
}

/// <summary>
/// PageMethod for reporting progress
/// </summary>
/// <returns></returns>
[System.Web.Services.WebMethod(EnableSession = true)]
public static object OperationProgress()
{
int operationProgress = 0;

if (HttpContext.Current.Session["OPERATION_PROGRESS"] != null)
operationProgress = (int)HttpContext.Current.Session["OPERATION_PROGRESS"];

return new { progress = operationProgress };
}
and modified JavaScript:
<script type="text/javascript">
$(document).ready(function () {
//Progressbar initialization
$("#progressbar").progressbar({ value: 0 });
//Button click event
$("#operation").click(function (e) {
//Disabling button
$("#operation").attr('disabled', 'disabled');
//Making sure that progress indicate 0
$("#progressbar").progressbar('value', 0);
//Call PageMethod which triggers long running operation
PageMethods.Operation(function(result) {
if (result) {
//Updating progress
$("#progressbar").progressbar('value', result.progress)
//Setting the timer
window.progressIntervalId = window.setInterval(function () {
//Calling PageMethod for current progress
PageMethods.OperationProgress(function (result) {
//Updating progress
$("#progressbar").progressbar('value', result.progress)
//If operation is complete
if (result.progress == 100) {
//Clear timer
window.clearInterval(window.progressIntervalId);
//Enable button
$("#operation").attr('disabled', '');
}
});
}, 500);
}
});
});
});
</script>
The effect is pretty much the same:
The samples for ASP.NET MVC and ASP.NET WebForms can be downloaded here. Enjoy.

23 comments:

Anonymous said...

Great article… just what I needed. Can you provide the download in a zip file?

Tomasz Pęczek said...

Sure, I'll upload it to Codeplex on weekend.

Tomasz Pęczek said...

Its available here: http://tpeczek.codeplex.com/releases/view/52932

Robert Tanenbaum said...

Tomasz, I downloaded the MVC version and compiled and ran it. It compiles OK and starts up OK. I click the button and it initiates, but the bar never gets updated. I set breakpoints in the source code and Operation was called, but OperationProgress never got called. Sometimes when I run it, OperationProgress gets called a few times and then hangs. The Webforms version works consistently. Thanks. R. Tanenbaum

Tomasz Pęczek said...

Hi Robert,

I will look into this tomorrow and let you know.

Greetings

Tomasz Pęczek said...

Hi Robert,

The bug was caused by caching. I have updated the sample with fix (and some other things), you can download it form my SVN repository at CodePlex - https://tpeczek.svn.codeplex.com/svn/trunk/MVC/jQuery%20UI%20Progressbar%20Example/. The Lib.Web.Mvc which is referenced by the sample can be found at the same repository - https://tpeczek.svn.codeplex.com/svn/trunk/MVC/Lib.Web.Mvc/v3.0.0.0/Lib.Web.Mvc/

Greetings

Robert Tanenbaum said...

Thank you. Your clue helped me solve it. I have not upgraded to MVC3 so I don't have the [NoCache] attribute. Instead I used the attribute
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
on the OperationProgress method. It is available in MVC2 and worked fine.
Thank you again.

Tomasz Pęczek said...

NoCacheAttribute is not from ASP.NET MVC 3, is from my Lib.Web.Mvc project. You can grab the source code and use it in your project --> https://tpeczek.svn.codeplex.com/svn/trunk/MVC/Lib.Web.Mvc/v3.0.0.0/Lib.Web.Mvc/NoCacheAttribute.cs

HostCheap said...

Hi,

I used your web form version and while it does work right away, I noticed that each time after the progress bar increments it "flashes" or "refreshes" and the progress indicator is reset to 0 before being incremented again.

Tomasz Pęczek said...

I'll check on this.

Amin Taheri said...

Hi, were you able to make any progress on this?

Tomasz Pęczek said...

Honestly I can't recreate the problem with current repository/download version.

DC said...

Is that possible to keep track of it after i have closed the browser? I mean, if the progress has not yet completed (eg:52%), i close the browser and i open it again, is that possible to see the progress? (maybe on 52..53..54%)
I guess the session will lose, we can't keep track. Is that a way to keep track after i have close the browser and resume..?

Gustavo said...

Great Article! Thks!

Anonymous said...

This is simple and great!

Anonymous said...

Hi

I'm tring to use the WabForm example but the progress counter is returning always 0. It seems that there is a probelm with the session param.

do u have any clue?
thanks

Tomasz Pęczek said...

I can't reproduce such an issue, are you sure you are using latest repository version and you haven't done any modifications?

sach said...

hi.
How i can using with file upload ?? (html4 over iframe). Now im using chunks uploading files over handler (webforms). Thanks.

Brian C said...

how would you make the progress bar only appear once you click on the button and have it disappear after it reaches 100%?

Second question - why does this not work with an

Brian C said...

sorry, the second question did not post because of the HTML tags... Second question - why does this not work with an asp button?

Tomasz Pęczek said...

For your first question you should modify the script to create the progres bar not on load but on click and then destroy it when it reaches one hundred (there is a method on progres bar to do it).

This will not work with asp buton because buton requires making full POST request and is waiting for standard response. There is no possibility for waiting in between.

n1y0 said...

Thank you.

profeverton said...

Hello, great article ... but what if in the process I have to update a gridview, how would the implementation?