ASP.NET MVC 3: Async jQuery progress indicator for long running tasks

If you have long running (server side) tasks in your ASP.NET MVC 3 web application it could be reasonable to provide the user with information about the current progress of this task. In this blog post I will show how such a progress indicator could be implemented using jQuery and AJAX.

The basic idea is as follows: Two actions exists on the server side; one action starts the (long running) task in a new thread and returns a unique identifier (GUID in this example) to the client and the second action returns the current (percentaged) status for a specific task.

For simplicity, in this example all (server side) functionality is implemented in the HomeController:

public class HomeController : Controller
{
  private static IDictionary<Guid, int> tasks = new Dictionary<Guid, int>();
 
  public ActionResult Index()
  {
    return View();
  }
 
  public ActionResult Start()
  {
    var taskId = Guid.NewGuid();
    tasks.Add(taskId, 0);
 
    Task.Factory.StartNew(() =>
    {
      for (var i = 0; i <= 100; i++)
      {
        tasks[taskId] = i; // update task progress
        Thread.Sleep(50); // simulate long running operation
      }
      tasks.Remove(taskId);
    });
 
    return Json(taskId);
  }
 
  public ActionResult Progress(Guid id)
  {
    return Json(tasks.Keys.Contains(id) ? tasks[id] : 100);
  }
}

The HomeController uses a Dictionary to save the progresses of all tasks. The Start action adds a new entry to this dictionary (with progress 0) and starts a new Task that simulates the long running operation. It is important to understand that this (time-consuming) operation is running in a separate thread and server immediately returns a result to the client (the unique identifier of the task), i.e. there is no HTTP request that is open as long as the task runs. The Progress action just returns the progress for a given GUID that uniquely identifies a task. Both actions return a JSON-result to enable easily processing of the data on the client side.

The client side functionality is implemented in the Index.cshtml view:

<script type="text/javascript">
 
function updateMonitor(taskId, status) {
  $("#" + taskId).html("Task [" + taskId + "]: " + status);
}
 
$(function () {
  $("#start").click(function (e) {
    e.preventDefault();
    $.post("Home/Start", {}, function (taskId) {
 
      // Init monitors
      $("#monitors").append($("<p id='" + taskId + "'/>"));
      updateMonitor(taskId, "Started");
 
      // Periodically update monitors
      var intervalId = setInterval(function () {
        $.post("Home/Progress", { id: taskId }, function (progress) {
          if (progress >= 100) {
            updateMonitor(taskId, "Completed");
          clearInterval(intervalId);
          } else {
            updateMonitor(taskId, progress + "%");
          }
        });
      }, 100);
    });
  });
});
</script>
 
<div id="monitors"></div>

On the client side the jQuery function $.post() is used to

  • first “call” the URL Home/Start to start a new task
  • and then periodically pass the returned GUID to the URL Home/Progress to get the current progress.

To show some feedback to the user a <p> tag is created for each started task and periodically updated with the current progress.

Note: The approach shown above supports multiple parallel running tasks. You can click the “start” link while other task(s) are still running.

The code shown above is only exemplary. In a real-world project one should implement a robust error handling to support for example situations where the tasks are interrupted. Furthermore it could be reasonable to limit the number of parallel tasks one user could start, since long running tasks are often need much resources.

You can download the Visual Studio 2010 project containing all the source code here.