December 2011

Volume 26 Number 12

The Cutting Edge - A Context-Sensitive Progress Bar for ASP.NET MVC

By Dino Esposito | December 2011

Dino EspositoMost users of a computer application want to receive appropriate feedback from the application whenever it embarks on a potentially lengthy operation. Implementing this pattern in a Windows application is no big deal, although it isn’t as straightforward as you might expect. Implementing the same pattern over the Web raises some additional difficulties, which are mostly related to the inherently stateless nature of the Web.

Particularly for Web developers, displaying some static feedback at the beginning of the operation is easy. So the focus of this column isn’t on how to display a simple string of text saying “Please wait” or an animated image. Instead, this column tackles the issue of reporting the status of remote operations, providing context-sensitive feedback that faithfully represents the status of the operation for a given session. In Web applications, lengthy tasks occur on the server, and there are no built-in facilities to push state information to the client browser. To achieve this, you need to build your own framework with a mix of client and server code. Libraries such as jQuery are helpful but don’t provide a built-in solution—not even in the wide selection of jQuery plug-ins.

In this column—the first of a short series—I’ll discuss the most common AJAX pattern you’ll find behind any context-sensitive progress framework, and build a solution tailor-made for ASP.NET MVC applications. If you’re a Web Forms developer, you might want to take a look at a couple of columns I wrote in in the summer of 2007 targeting Web Forms and AJAX (see my column list at bit.ly/psNZAc).

The ‘Progress Indicator’ Pattern

The fundamental problem with an AJAX-based progress report is providing feedback about the status of the operation while the user is waiting for a server response. Put another way: The user starts an AJAX operation that takes a while to complete; meanwhile, the user receives updates about the progress made. The architectural problem is that the user isn’t going to receive partial responses; the operation returns its response only when all the server-side work has been done. To bring partial responses back to the client, some sort of synchronization between the AJAX client and the server application must be established. The “Progress Indicator” pattern addresses this point.

The pattern suggests that you architect ad hoc server operations that write information about their status to a known location. The status is overwritten every time the operation makes a significant amount of progress. At the same time, the client opens a second channel and makes periodic reads from the same known location. In this way, any changes are promptly detected and reported to the client. More important, the feedback is context-sensitive and real. It represents effective progress made on the server and isn’t based on guesses or estimates.

The implementation of the pattern requires that you write your server operations so they’re aware of their roles. For example, suppose you implement a server operation based on a workflow. Before starting each significant step of the workflow, the operation will invoke some code that updates a durable store with some task-related information. The durable store can be a database table or a piece of shared memory. The task-related information can be a number indicating the percentage of work accomplished or a message that describes the ongoing task.

At the same time, you need a JavaScript-based service that concurrently reads the text from the durable store and brings it back to the client. Finally, the client will use some JavaScript code to merge the text to the existing UI. This can result in some simple text displayed in a DIV tag or in something more sophisticated such as an HTML-based progress bar.

Progress Indicator in ASP.NET MVC

Let’s see what it takes to implement the Progress Indicator pattern in ASP.NET MVC. The server operation is essentially a controller action method. The controller can be either synchronous or asynchronous. Asynchrony in controllers is beneficial only to the health and responsiveness of the server application; it doesn’t have any impact on the time the user has to wait to get a response. The Progress Indicator pattern works well with any type of controller.

To invoke and then monitor a server operation, you need a bit of AJAX. However, the AJAX library shouldn’t be limited to placing the request and waiting for the response. The library should also be able to set up a timer that periodically fires a call to some endpoint that returns the current status of the operation. For this reason, jQuery or the native XMLHttpRequest object are necessary, but not sufficient. So I created a simple JavaScript object to hide most of the extra steps required with a monitored AJAX call. From within an ASP.NET MVC view, you invoke the operation via the JavaScript object.

The controller method responsible for the operation will use the server-side portion of the framework to store the current status in a known (and shared) location, such as the ASP.NET cache. Finally, the controller must expose a common endpoint for the timed requests to call in order to read status in real time. Let’s first see the whole framework in action and then move on to explore the internals.

Introducing the SimpleProgress Framework

Written specifically for this article, the SimpleProgress Framework (SPF) consists of a JavaScript file and a class library. The class library defines one key class—the ProgressManager class—that governs the execution of the task and any monitoring activity. Figure 1 shows a sample controller action method that uses the framework. Note that this (potentially long-running) code should actually go in an asynchronous controller to avoid blocking an ASP.NET thread for too long.

Figure 1 A Controller Action Method Using the SimpleProgress Framework

public String BookFlight(String from, String to)
{
  var taskId = GetTaskId();
  // Book first flight
  ProgressManager.SetCompleted(taskId,
    String.Format("Booking flight: {0}-{1} ...", from, to));
  Thread.Sleep(2000);
  // Book return flight
  ProgressManager.SetCompleted(taskId,
    String.Format("Booking flight: {0}-{1} ...", to, from));
  Thread.Sleep(1000);
  // Book return
  ProgressManager.SetCompleted(taskId,
    String.Format("Paying for the flight ..."));
  Thread.Sleep(2000);
  // Some return value
  return String.Format("Flight booked successfully");
}

As you can see, the operation is based on three main steps. For the sake of simplicity, the actual action is omitted and is replaced with a Thread.Sleep call. More important, you can see three calls to SetCompleted that actually write the current status of the method to a shared location. The details of the location are buried in the ProgressManager class. Figure 2 shows what’s required to invoke and monitor a controller method.

Figure 2 Invoking and Monitoring a Method via JavaScript

<script type="text/javascript">
  $(document).ready(function () {
    $("#buttonStart").bind("click", buttonStartHandler);
  });
  function buttonStartHandler() {
    new SimpleProgress()
    .setInterval(600)
    .callback(
      function (status) { $("#progressbar2").text(status); },
      function (response) { $("#progressbar2").text(response); })
    .start("/task/bookflight?from=Rome&to=NewYork", "/task/progress");
  }
</script>

Note that for the sake of readability, I kept the buttonStartHandler of Figure 2 out of the document’s ready handler. By doing so, however, I add a bit of pollution to the global JavaScript scope by defining a new globally visible function.

You first set a few parameters such as the URL to be called back to grab the current status and the callbacks to be invoked to update the progress bar and to refresh the UI once the lengthy task has completed. Finally, you start the task.

The controller class must incorporate some extra capabilities. Specifically, it must expose a method to be called back. This code is relatively standard, and I hardcoded it into a base class from which you can inherit your controller, as shown here:

public class TaskController : SimpleProgressController
{
  ...
  public String BookFlight(String from, String to)
  {
    ...
  }
}

The entire SimpleProgressController class is shown in Figure 3.

Figure 3 The Base Class for Controllers that Incorporate Monitored Actions

public class SimpleProgressController : Controller
{
  protected readonly ProgressManager ProgressManager;
  public SimpleProgressController()
  {
    ProgressManager = new ProgressManager();
  }
  public String GetTaskId()
  {
    // Get the header with the task ID
    var id = Request.Headers[ProgressManager.HeaderNameTaskId];
    return id ?? String.Empty;
  }
  public String Progress()
  {
    var taskId = GetTaskId();
    return ProgressManager.GetStatus(taskId);
  }
}

The class has two methods. GetTaskId retrieves the unique task ID that represents the key to retrieve the status of a particular call. As you’ll see in more detail later, the task ID is generated by the JavaScript framework and sent over with each request using a custom HTTP header. The other method you find on the SimpleProgressController class represents the public (and common) endpoint that the JavaScript framework will call back to get the status of a particular task instance.

Before I get into some implementation details, Figure 4 will give you a concrete idea of the results the SPF allows you to achieve.

The Sample Application in Action
Figure 4 The Sample Application in Action

The ProgressManager Class

The ProgressManager class supplies the interface to read and write the current status to a shared store. The class is based on the following interface:

public interface IProgressManager
{
  void SetCompleted(String taskId, Int32 percentage);
  void SetCompleted(String taskId, String step);
  String GetStatus(String taskId);
}

The SetCompleted method stores the status as a percentage or a simple string. The GetStatus method reads any content back. The shared store is abstracted by the IProgressDataProvider interface:

public interface IProgressDataProvider
{
  void Set(String taskId, String progress, Int32 durationInSeconds=300);
  String Get(String taskId);
}

The current implementation of the SPF provides just one progress data provider that saves its content in the ASP.NET cache. The key to identify the status of each request is the task ID. Figure 5 shows a sample progress data provider.

Figure 5 A Progress Data Provider Based On the ASP.NET Cache Object

public class AspnetProgressProvider : IProgressDataProvider
{
  public void Set(String taskId, String progress, Int32 durationInSeconds=3)
  {
    HttpContext.Current.Cache.Insert(
      taskId,
      progress,
      null,
      DateTime.Now.AddSeconds(durationInSeconds),
      Cache.NoSlidingExpiration);
  }
  public String Get(String taskId)
  {
    var o = HttpContext.Current.Cache[taskId];
    if (o == null)
      return String.Empty;
    return (String) o;
  }
}

As mentioned, each request for a monitored task is associated with a unique ID. The ID is a random number generated by the JavaScript framework and passed from the client to the server through a request HTTP header.

The JavaScript Framework

One of the reasons the jQuery library became so popular is the AJAX API. The jQuery AJAX API is powerful and feature-rich, and it’s endowed with a list of shorthand functions that make placing an AJAX call a breeze. However, the native jQuery AJAX API doesn’t support progress monitoring. For this reason, you need a wrapper API that uses jQuery (or any other JavaScript framework you might like) to place the AJAX call while ensuring that a random task ID is generated for each call and the monitor service is activated. Figure 6 shows an excerpt from the SimpleProgress-Fx.js file in the accompanying code download that illustrates the logic behind the start of a remote call.

Figure 6 The Script Code to Invoke the SimpleProgress Framework

var SimpleProgress = function() {
  ...
  that.start = function (url, progressUrl) {
    that._taskId = that.createTaskId();
    that._progressUrl = progressUrl;
    // Place the AJAX call
    $.ajax({
      url: url,
      cache: false,
      headers: { 'X-SimpleProgress-TaskId': that._taskId },
      success: function (data) {
        if (that._taskCompletedCallback != null)
          that._taskCompletedCallback(data);
        that.end();
      }
    });
    // Start the callback (if any)
    if (that._userDefinedProgressCallback == null)
      return this;
    that._timerId = window.setTimeout(
      that._internalProgressCallback, that._interval);
  };
}

Once the task ID is generated, the function adds the ID as a custom HTTP header to the AJAX caller. Right after triggering the AJAX call, the function sets up a timer that periodically invokes a callback. The callback reads the current status and passes the result to a user-defined function for updating the UI.

I’m using jQuery to perform the AJAX call; in this regard, it’s important that you turn off browser caching for AJAX calls. In jQuery, caching is on by default and is automatically turned off for certain data types such as script and JSON With Padding (JSONP).

Not an Easy Task

Monitoring ongoing operations isn’t an easy task in Web applications. Solutions based on polling are common but not inevitable. An interesting GitHub project that implements persistent connections in ASP.NET is SignalR (github.com/signalr). Using SignalR, you can solve the same problem discussed here without polling for changes.

In this column, I discussed a sample framework (both client and server) that attempts to simplify the implementation of a context-sensitive progress bar. While the code is optimized for ASP.NET MVC, the underlying pattern is absolutely general and can be employed in ASP.NET Web Forms applications as well. If you download and experiment with the source code, feel free to share your thoughts and feedback.


Dino Esposito is the author of “Programming Microsoft ASP.NET MVC3” (Microsoft Press, 2011) and coauthor of “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2008). Based in Italy, Esposito is a frequent speaker at industry events worldwide. You can follow him on Twitter at twitter.com/despos.

Thanks to the following technical experts for reviewing this column: Damian Edwards, Phil Haack and Scott Hunter