March 2012

Volume 27 Number 03

Cutting Edge - Build a Progress Bar with SignalR

By Dino Esposito | March 2012

Dino EspositoIn the past two installments of this column I discussed how to build an ASP.NET solution for the evergreen problem of monitoring the progress of a remote task from the client side of a Web application. Despite the success and adoption of AJAX, a comprehensive and widely accepted solution for displaying a context-sensitive progress bar within a Web application without resorting to Silverlight or Flash is still lacking.

To be honest, there aren't many ways in which one can accomplish this. You might craft your own solution if you want, but the underlying pattern won’t be that different from what I presented—specifically targeting ASP.NET MVC—in the past columns. This month, I’m back to the same topic, but I’ll discuss how to build a progress bar using a new and still-in-progress library: SignalR.

SignalR is a Microsoft .NET Framework library and jQuery plug-in being developed by the ASP.NET team, possibly to be included in future releases of the ASP.NET platform. It presents some extremely promising functionality that's currently missing in the .NET Framework and that more and more developers are demanding.

SignalR at a Glance

SignalR is an integrated client-and-server library that enables browser-based clients and ASP.NET-based server components to have a bidirectional and multistep conversation. In other words, the conversation isn’t limited to a single, stateless request/response data exchange; rather, it continues until explicitly closed. The conversation takes place over a persistent connection and lets the client send multiple messages to the server and the server reply—and, much more interesting—send asynchronous messages to the client.

It should come as no surprise that the canonical demo I’ll use to illustrate the main capabilities of SignalR is a chat application. The client starts the conversation by sending a message to the server; the server—an ASP.NET endpoint—replies and keeps listening for new requests.

SignalR is specifically for a Web scenario and requires jQuery 1.6 (or newer) on the client and ASP.NET on the server. You can install SignalR via NuGet or by downloading the bits directly from the GitHub repository at github.com/SignalR/SignalR. Figure 1 shows the NuGet page with all SignalR packages. At minimum, you need to download SignalR, which has dependencies on SignalR.Server for the server-side part of the framework, and SignalR.Js for the Web-client part of the framework. The other packages you see in Figure 1 serve more specific purposes such as providing a .NET client, a Ninject dependency resolver and an alternate transportation mechanism based on HTML5 Web sockets.

SignalR Packages Available on the NuGet Platform
Figure 1 SignalR Packages Available on the NuGet Platform

Inside the Chat Example

Before I attempt to build a progress bar solution, it would be useful to get familiar with the library by taking a look at the chat example distributed with the downloadable source code (msdn.com/magazine/msdnmag0312) and other information referenced in the (few) related posts currently available on the Web. Note, though, that SignalR is not a released project.

In the context of an ASP.NET MVC project, you start by referencing a bunch of script files, as shown here:

<script src="@Url.Content("~/Scripts/jquery-1.6.4.min.js")"
  type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.signalr.min.js")"
  type="text/javascript"></script>
<script src="@Url.Content("~/signalr/hubs")"
  type="text/javascript"></script>

Note that there’s nothing specific to ASP.NET MVC in SignalR, and the library can be used equally well with Web Forms applications.

An interesting point to emphasize is that the first two links reference a specific script file. The third link, instead, still references some JavaScript content, but that content is generated on the fly—and that depends on some other code you have within the host ASP.NET application. Also note that you need the JSON2 library if you intend to support versions of Internet Explorer prior to version 8.

Upon the page loading, you finalize the client setup and open the connection. Figure 2 shows the code you need. You might want to call this code from within the ready event of jQuery. The code binds script handlers to HTML elements—unobtrusive JavaScript—and prepares the SignalR conversation.

Figure 2 Setting Up the SignalR Library for a Chat Example

<script type="text/javascript">
  $(document).ready(function () {    // Add handler to Send button
    $("#sendButton").click(function () {
      chat.send($('#msg').val());
    });
    // Create a proxy for the server endpoint
    var chat = $.connection.chat; 
    // Add a client-side callback to process any data
    // received from the server
    chat.addMessage = function (message) {
      $('#messages').append('<li>' + message + '</li>');
    };
    // Start the conversation
    $.connection.hub.start();
  });
</script>

It's worth noting that the $.connection object is defined in the SignalR script file. The chat object, in contrast, is a dynamic object in the sense that its code is generated on the fly and is injected into the client page via the Hub script reference. The chat object is ultimately a JavaScript proxy for a server-side object. At this point it should be clear that the client code in Figure 2 means (and does) little without a strong server-side counterpart.

The ASP.NET project should include a reference to the SignalR assembly and its dependencies such as Microsoft.Web.Infrastructure. The server-side code includes a managed class that matches the JavaScript object you created. With reference to the code in Figure 2, you need to have a server-side object with the same interface as the client-side Chat object. This server class will inherit from the Hub class defined in the SignalR assembly. Here’s the class signature:

using System;
using SignalR.Hubs;
namespace SignalrProgressDemo.Progress
{
  public class Chat : Hub
  {
    public void Send(String message)
    {
      Clients.addMessage(message);
    }
  }
}

Every public method in the class must match a JavaScript method on the client. Or, at least, any method invoked on the JavaScript object must have a matching method on the server class. So the Send method you see invoked in the script code of Figure 2 ends up placing a call into the Send method of the Chat object, as defined earlier. To send data back to the client, the server code uses the Clients property on the Hub class. The Clients member is of type dynamic, which enables it to reference dynamically deter­mined objects. In particular, the Clients property contains a reference to a server-side object built after the interface of the client object: the Chat object. Because the Chat object in Figure 2 has an addMessage method, the same addMessage method is expected to be exposed also by the server-side Chat object.

Toward a Progress Bar Demo

Now let’s use SignalR to build a notification system that reports to the client any progress being made on the server during a possibly lengthy task. As a first step, let’s create a server-side class that encapsulates the task. The name you assign to this class, while arbitrarily chosen, will affect the client code you’ll write later. This simply means you have one more reason to choose the class name with care. Even more important, this class will inherit from a SignalR provided class named Hub. Here’s the signature:

public class BookingHub : Hub
{
  ...
}

The BookingHub class will have a few public methods—mostly void methods accepting any sequence of input parameters that makes sense for their intended purpose. Every public method on a Hub class represents a possible endpoint for the client to invoke. As an example, let’s add a method to book a flight:

public void BookFlight(String from, String to)
{
  ...
}

This method is expected to contain all the logic that executes the given action (that is, booking a flight). The code will also contain at various stages calls that in some way will report any progress back to the client. Let’s say the skeleton of method BookFlight looks like this:

public void BookFlight(String from, String to)
{
  // Book first leg  var ref1 = BookFlight(from, to);  // Book return flight
  var ref2 = BookFlight(to, from);
  // Handle payment
  PayFlight(ref1, ref2);
}

In conjunction with these main operations, you want to notify the user about the progress made. The Hub base class offers a property called Clients defined to be of type dynamic. In other words, you’ll invoke a method on this object to call back the client. The form and shape of this method, though, are determined by the client itself. Let’s move to the client, then.

As mentioned, in the client page you’ll have some script code that runs when the page loads. If you use jQuery, the $(document).ready event is a good place for running this code. First, you get a proxy to the server object:

var bookingHub = $.connection.bookingHub;
// Some config work
...
// Open the connection
$.connection.hub.start();

The name of the object you reference on the $.connection SignalR native component is just a dynamically created proxy that exposes the public interface of the BookingHub object to the client. The proxy is generated via the signalr/hubs link you have in the <script> section of the page. The naming convention used for names is camelCase, meaning that class BookingHub in C# becomes object bookingHub in JavaScript. On this object you find methods that match the public interface of the server object. Also, for methods, the naming convention uses the same names, but camelCased. You can add a click handler to an HTML button and start a server operation via AJAX, as shown here:

bookingHub.bookFlight("fco", "jfk");

You can now define client methods to handle any response. For example, you can define on the client proxy a displayMessage method that receives a message and displays it through an HTML span tag:

bookingHub.displayMessage = function (message) {
  $("#msg").html(message);
};

Note that you’re responsible for the signature of the display­Message method. You decide what’s being passed and what type you expect any input to be.

To close the circle, there’s just one final issue: who’s calling displayMessage and who’s ultimately responsible for passing data? It’s the server-side Hub code. You call displayMessage (and any other callback method you want to have in place) from within the Hub object via the Clients object. Figure 3 shows the final version of the Hub class.

Figure 3 The Final Version of the Hub Class

public void BookFlight(String from, String to)
{
  // Book first leg
  Clients.displayMessage(    String.Format("Booking flight: {0}-{1} ...", from, to));
  Thread.Sleep(2000);
  // Book return
  Clients.displayMessage(    String.Format("Booking flight: {0}-{1} ...", to, from));
  Thread.Sleep(3000);
  // Book return
  Clients.displayMessage(    String.Format("Booking flight: {0}-{1} ...", to, from));
  Thread.Sleep(2000);
  // Some return value
  Clients.displayMessage("Flight booked successfully.");
}

Note that in this case, the displayMessage name must match perfectly the case you used in the JavaScript code. If you mistype it to something such as DisplayMessage, you won’t get any exception—but no code will execute, either.

The Hub code is implemented as a Task object, so it gets its own thread to run and doesn’t affect the ASP.NET thread pool.

If a server task results in asynchronous work being scheduled, it will pick up a thread from the standard worker pool. The advantage is, SignalR request handlers are asynchronous, meaning that while they’re in the wait state, waiting for new messages, they aren’t using a thread at all. When a message is received and there’s work to be done, an ASP.NET worker thread is used.

A True Progress Bar with HTML

In past columns, as well as in this one, I used the term progress bar frequently without ever showing a classic gauge bar as an example of the client UI. Having a gauge bar is only a nice visual effect and doesn’t require more complex code in the async infrastructure. However, Figure 4 shows the JavaScript code that builds a gauge bar on the fly given a percentage value. You can change the appearance of the HTML elements via proper CSS classes.

Figure 4 Creating an HTML-Based Gauge Bar

var GaugeBar = GaugeBar || {};
GaugeBar.generate = function (percentage) {
  if (typeof (percentage) != "number")
    return;
  if (percentage > 100 || percentage < 0)
    return;
  var colspan = 1;
  var markup = "<table class='gauge-bar-table'><tr>" +
    "<td style='width:" + percentage.toString() +
    "%' class='gauge-bar-completed'></td>";
  if (percentage < 100) {
    markup += "<td class='gauge-bar-tobedone' style='width:" +
      (100 - percentage).toString() +
      "%'></td>";
    colspan++;
  }
  markup += "</tr><tr class='gauge-bar-statusline'><td colspan='" +
    colspan.toString() +
    "'>" +
    percentage.toString() +
    "% completed</td></tr></table>";
  return markup;
}

You call this method from a button click handler:

bookingHub.updateGaugeBar = function (perc) {
  $("#bar").html(GaugeBar.generate(perc));
};

The updateGaugeBar method is therefore invoked from another Hub method that just uses a different client callback to report progress. You can just replace displayMessage used previously with updateGaugeBar within a Hub method.

Not Just Web Clients

I presented SignalR primarily as an API that requires a Web front end. Although this is probably the most compelling scenario in which you might want to use it, SignalR is in no way limited to supporting just Web clients. You can download a client for .NET desktop applications, and another client will be released soon to support Windows Phone clients.

This column only scratched the surface of SignalR in the sense that it presented the simplest and most effective approach to program it. In a future column, I’ll investigate some of the magic it does under the hood and how packets are moved along the wire. Stay tuned.


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, he’s a frequent speaker at industry events worldwide. You can follow him on Twitter at twitter.com/despos.

Thanks to the following technical expert for reviewing this article: Damian Edwards