Monitor Server Tasks with ASP.NET SignalR and Bootstrap

Despite the fact that browsers were designed specifically to get information from the server only by requesting or 'pulling' it, developers have always yearned to be able to push data to browsers from the server. Typically, it would be to display, within a web page within the browser, the progress of a long-running task. Now ASP.NET SignalR and Bootstrap make it possible, with care. Dino explains how.

A generation of web developers sought an easy and effective way of indicating the progress of a server-based process from within a web page. They struggled determinedly to build one that could be updated easily by server-side notifications. Typically, it would involve these stages.

  • Users click a button to start a potentially-lengthy task on the server.
  • The main user interface remains intact, and an IFRAME or a new DIV section is revealed that shows any progress in the task.
  • As the task progresses on the server, updates are reported on the client and magically show up in the IFRAME or DIV.

The problem is that there’s no direct way for a web client to be updated from the server. This is intrinsic to the way that the entire web works. Requests to the server get a response but there is no way for a server to send autonomous messages that can be pushed to one or more clients. For years, developers worked around the problem using polling before ASP.NET SignalR arrived to offer a powerful framework that takes the traditional polling approach to the next level. ASP.NET SignalR offers a convenient API for any sort of server-to-client push communication. ASP.NET SignalR isn’t new; it’s been around for a few years now. The architecture and the steps to set up an ASP.NET SignalR solution have been widely treated in a variety of articles.

In this article, I’ll be describing how ASP.NET SignalR technology provides live updates on persistent connections, and show how you can use this to implement progress bars, and Bootstrap-based progress bars in particular.

A Word about Web Sockets and Server-Sent Events

I mentioned in a recent article I wrote for Simple-Talk, ASP.NET SignalR: Old-fashioned Polling, Just Done Better, that ASP.NET SignalR falls back to using long polling if that is all that is available, but It prefers to use web sockets when browsers and web servers allow. Web sockets require IIS 8.0 and Windows Server 2012. Web sockets also work in IIS 8.0 Express but not on Windows 7 and earlier. Having said that, any solution based on ASP.NET SignalR will work as expected regardless of the version of either webserver or operating system but it does that by using HTTP long polling where nothing better is available.

The HTTP 1.0 protocol establishes that a new connection is set up between the browser and web server for every request. Once the response for the request has been sent, the server closes the connection. The HTTP 1.1 protocol recommends that browsers should reuse the same connection to download multiple resources from the same page. This ends up saving the cost of repeated setup of TCP connections. It’s a small but worthwhile improvement because it maintains the rule that one response is sent per request. Long polling is a trick that can be used even over HTTP 1.1 in order to have results from server and clients working in sync. When a new request arrives, the server will, if it can, send an immediate response and close the connection. If it can’t, then it just sends headers back and waits for the entire response to become available. The browser, on its end, closes the pending connection if no response is fully received in a given amount of time. Right after closing a pending connection, the browser reiterates the request and the loop continues until a full response is obtained.

The WebSocket protocol, in contrast, enables full-duplex communication over TCP between a generic client and a generic server. It extends the raw TCP protocol in that it introduces the concept of a message rather using plain bytes as TCP. The full-duplex nature of the protocol makes it substantially different from HTTP, but at the same time frameworks can be built to adapt it to support a full-duplex HTTP channel between a configured pair of browser and web server. The WebSocket protocol is both an IETF and W3C standard.

Yet another option for server notification over the web is based on the concept of HTTP streaming and finds its more recent expression in the W3C EventSource API: http://www.w3.org/TR/eventsource/. The idea is that the server maintains an open connection with the browser so that the server can send multiple notifications with an ad hoc MIME type which will be then exposed back to the browser’s code as DOM events. The EventSource API is also referred to in some articles as ‘Server-Sent’ events.

In this regard, it should be noted that streaming messages to a browser cuts across the inherent design of the internal architecture of the web server. In IIS, for example, you need an update to http.sys to support Web Sockets. Similar radical changes may be required to support server-sent events. Note that servers such as NodeJS are much better-placed to support Server-sent events because of their event-based nature, in contrast to the thread-based nature of classic servers such as IIS.

Setting up the Progress Bar Demo

Let’s assume you already have an ASP.NET MVC Visual Studio project up and running and configured to use ASP.NET SignalR and Bootstrap. For more information, you can refer to the article ASP.NET SignalR: Old-fashioned Polling, Just Done Better. Let’s add a Razor view with a button to start a remote task.

The startTask JavaScript function simply invokes an action on a MVC controller. Here’s some possible code for it:

The Lengthy method on TaskController is the classic, potentially lengthy, task that may take a while to complete and for which notifications to the client are appropriate and convenient for the user. For the sake of the demo, here’s some dummy code that just simulates the step-by-step execution of a typical business workflow.

As you can see, the task is articulated in several steps, each of which marks an amount of work done. To keep things as general as possible I envisioned a programming API made of three methods: NotifyStart, NotifyProgress and NotifyEnd. All these methods must be defined in the ASP.NET SignalR hub class. Here’s a possible implementation of the hub class.

There’s a question in this example that can’t be avoided. The idea is that the client-the user connected via the browser-clicks a button to start a business task. Both the user and the task should be clearly identified. Realistically, you’ll have the user logged into the system and can use her name to access the collection of connections that match that account. The following code will just send any response back to all connections that relate to a given user. This normally means updating one client except when the same user happens to be connected over several devices.

If the application works with logged-in users then success is almost a foregone conclusion. It’s a bit more complicated if the application distinguishes tasks but not users. For example, imagine you have a web site that can be used by anonymous users to request potentially lengthy tasks. A good example is a web site whose free or trial edition allows users to convert upload and documents anonymously. You still need to find a way for your ASP.NET SignalR backend to return response only to interested clients.

Mapping Anonymous Users to SignalR Connections

You have two ways to manage things so that the response is returned only to the actual caller when the caller is anonymous. One approach entails you having to track the ID of the ASP.NET SignalR connection and let it trickle down to the hub where you pass data back to the callers. The interesting thing is that you should make sure the connection is established before you attempt to use it. Here’s a better way to repaint the user interface.

The button is now disabled by default and it is turned on when the connection with the ASP.NET SignalR backend is established and the connection ID is known. This makes the case for using the done() callback in the initialization code of ASP.NET SignalR.

With this code in place, the start-task button becomes enabled only when the connection is set. The connection ID is saved to a JavaScript variable that is global to the page. The controller method receives the connection ID as an argument:

The connection ID argument is passed around to any hub method, being called as the task progresses.

In the hub, you just need to filter the clients by the connection ID. Here’s how:

The second approach is to make sure that only the actual caller is getting any response and consists of giving each request a unique ID-for example a task ID string. This is done on the client in JavaScript before the request is placed. You can use a GUID or a random string generated via JavaScript. The task ID is passed around to the controller method in much the same way we did with the connection ID in the previous example.

The task ID makes it to the hub code and here it is passed back to the client code that actually reacts to any server progress.

It is interesting to notice that, in this case, the response is sent back to all clients that are currently connected regardless of their interest. The task of filtering unwanted responses falls to the client. Any work related to that aspect, therefore, is moved to the client.

In this case currentTask is a JavaScript variable global to the page that stores the task ID as it is generated. As you can see, the client-side callback gets invoked; it updates the user interface only if the response refers to the task ID it contributed to start. The figure below shows the actual results of the trick: it’s just what you expect except that, without any response filtering, the updates will hit all of the clients in the order in which they are generated on the server. Even with two browser window you can see a crazy effect!

2281-0968e3d1-b657-4938-84aa-a5c8f6dc9e7

At first sight, it may seem that the tactic of sending a response to all clients and letting the clients deal with sorting out relevance is less than optimal, especially when the number of clients is high. This is only one way to look at it. If the work to filter gets heavy, then it may seem like optimization to split that effort on the various clients: Then, the server doesn’t have to do any extra work to decide about the response for all waiting clients. Given the mechanics of long polling (and web sockets, if supported) a connection stands even if there’s no response ready, so server resources are still under pressure and there’s more relief in not doing any extra work than in just sending some response to pending connections.

So much for the data transfer, let’s see now how to make the user interface appealing with the Bootstrap’s progress bar HTML component.

Displaying a Progress Bar

The primary merit of Bootstrap is the way that it refreshes the taxonomy of HTML elements well beyond the native set of HTML5 elements. HTML doesn’t provide a native tag that browsers will render and animate as a progress bar, but Bootstrap does a very good job of extending and expanding a basic DIV element.

That markup, when styled by Bootstrap, renders out as an empty progress bar. To show some color that gives life and progress to the component you must add a width attribute to the DIV decorated with the progress-bar CSS class.

This is precisely what the aforementioned code does via jQuery to reflect server-side notifications.

Note that the code uses ARIA (Accessible Rich Internet Applications) attributes to preserve accessibility of the resulting markup. ARIA attributes along with the role attribute don’t change anything in the way that regular desktop browsers render the page. However, they make a whole world of difference in readers used by people with disabilities.

Summary

The duo ASP.NET SignalR and Bootstrap at last make an old dream of web developers come true. Now it is possible to start a potential lengthy task from the client and be informed about the progress of it as it happens on the server. It still requires a bit of care and knowledge of the intricacies of ASP.NET SignalR but it’s never been that easy as it is today.