xhroot

Live Updates Using SignalR

Server push (AKA long polling and Comet) is a relatively recent technology where servers initiate data transfer to connected browsers. Contrast this with the normal request-response interaction shown below:

For a long running job, the browser would have to constantly pester the server for updates. With server push, the browser could initiate the job, then sit back and let the server respond at its leisure:

For Google App Engine developers this is implemented by the Channel API. There are also third party services like Pusher and Beaconpush that provide this capability through their API. And recently, thanks to David Fowler and Damian Edwards, .NET developers can also integrate this technology into their projects using SignalR.

I’ve cooked up a small demo that uses SignalR to demonstrate one solution to the problem of conflicting updates. When multiple users act on the same set of information, it’s easy for it to get out of sync. This demo uses SignalR’s Hub to push updates to the users the moment they occur so they can immediately act on the changes.

Our application is a student registry form. Users can update a student’s name, GPA and enrollment status. A server section, visible only for demo purposes, shows us the state of the database. Because the most recent data is always pushed to the browser, we can take immediate action instead of discovering a save conflict at the end of a long form.

Shown right is your typical N-tier MVC stack. Browser requests are handled by the controller, data is passed to the service for the real legwork, the DB is consulted as necessary and the data is passed back up the chain to the browser. Fantastic. Where SignalR comes into play is when the StudentService decides a legitimate update has taken place. It then notifies the Hub that a new update has occurred and it should broadcast the new record to all listening browsers.

Let’s take a look at the setup.

1
2
3
4
5
6
var req = System.Web.HttpContext.Current.Request;
var path = req.Url.Scheme + "://" + req.Url.Authority +
  req.ApplicationPath;

_studentService = new StudentService(new DataClassesDataContext(),
  new HubConnection(path));

The HubConnection establishes the base URL that the client side API will use to watch for updates. This is done in the controller and injected into the StudentService. Here’s the javascript setup:

1
2
3
4
// Start hub; load clientId when started.
$.connection.hub.start(function () {
  _clientId = $.connection.hub.id;
});

This starts the hub on the client side. Once started, a unique id is assigned to this browser which comes in handy as we’ll see later. Next we look at the Hub,

1
2
3
4
5
6
7
8
public class StudentHub : Hub
{
  public void BroadcastUpdatedStudent(StatusTransport<StudentViewModel>
    transport)
  {
    Clients.updateStudent(transport);
  }
}

and its corresponding javascript:

1
2
3
4
5
// Define the callback that allows the server to send data.
var _studentHub = $.connection.studentHub;
_studentHub.updateStudent = function (result) {
  //...
};

StudentHub derives from the abstract Hub. The hub only needs one method for this demo and that method sends out the updated data. It calls a javascript method called updateStudent and sends it one argument - transport. By the way, StatusTransport is a wrapper around the data to be sent - StudentViewModel - and is used to attach additional information about the request (e.g., operation status, error messages). In the javascript, the function name should match the method that is invoked from StudentHub; result is the javascript object representation of the transport object sent by StudentHub. When Clients.updateStudent executes on the server, all connected browsers will execute this javascript function locally.

Recall the video demo above. When the student is saved, all other browsers immediately show a dialog notifying them that an update recently occurred with the option to accept the recent changes. Note also that the browser that issues the save will receive an updated record from both the Ajax save response and the StudentHub. We use the ClientId to check for the identity of the sender and ignore the duplicate update when the client id is self.

To contrast this behavior with the conventional approach, disable the hub by commenting out this line in StudentService:

1
        BroadcastStudent(transport);

Now, concurrency violations will only detected at save time.

I’ve built this example as an MVC project, but Webforms could be used as well and I’ve included a WCF service as an example. To use it, in the Index.cshtml view, change the serviceUrl ternary to true.

To wrap up,

  • Pros:
    • Very responsive UX. Events can be reported within milliseconds of a database save.
    • Allows interaction with other connected clients (e.g., customer service chat).
    • Can be added to existing projects.
  • Cons:
    • Introduces some complexity in the code, especially on the client side. More states to keep track of.

The source for this demo is available on github: Registry.

Comments