Real-world SignalR example, ditching ghetto long polling
- by Jeff
One of the highlights of BUILD last week was the announcement that SignalR, a framework for real-time client to server (or cloud, if you will) communication, would be a real supported thing now with the weight of Microsoft behind it. Love the open source flava! If you aren’t familiar with SignalR, watch this BUILD session with PM Damian Edwards and dev David Fowler. Go ahead, I’ll wait. You’ll be in a happy place within the first ten minutes. If you skip to the end, you’ll see that they plan to ship this as a real first version by the end of the year. Insert slow clap here. Writing a few lines of code to move around a box from one browser to the next is a way cool demo, but how about something real-world? When learning new things, I find it difficult to be abstract, and I like real stuff. So I thought about what was in my tool box and the decided to port my crappy long-polling “there are new posts” feature of POP Forums to use SignalR. A few versions back, I added a feature where a button would light up while you were pecking out a reply if someone else made a post in the interim. It kind of saves you from that awkward moment where someone else posts some snark before you. While I was proud of the feature, I hated the implementation. When you clicked the reply button, it started polling an MVC URL asking if the last post you had matched the last one the server, and it did it every second and a half until you either replied or the server told you there was a new post, at which point it would display that button. The code was not glam: // in the reply setup
PopForums.replyInterval = setInterval("PopForums.pollForNewPosts(" + topicID + ")", 1500);
// called from the reply setup and the handler that fetches more posts
PopForums.pollForNewPosts = function (topicID) {
$.ajax({
url: PopForums.areaPath + "/Forum/IsLastPostInTopic/" + topicID,
type: "GET",
dataType: "text",
data: "lastPostID=" + PopForums.currentTopicState.lastVisiblePost,
success: function (result) {
var lastPostLoaded = result.toLowerCase() == "true";
if (lastPostLoaded) {
$("#MorePostsBeforeReplyButton").css("visibility", "hidden");
} else {
$("#MorePostsBeforeReplyButton").css("visibility", "visible");
clearInterval(PopForums.replyInterval);
}
},
error: function () {
}
});
};
What’s going on here is the creation of an interval timer to keep calling the server and bugging it about new posts, and setting the visibility of a button appropriately. It looks like this if you’re monitoring requests in FireBug:
Gross.
The SignalR approach was to call a message broker when a reply was made, and have that broker call back to the listening clients, via a SingalR hub, to let them know about the new post. It seemed weird at first, but the server-side hub’s only method is to add the caller to a group, so new post notifications only go to callers viewing the topic where a new post was made. Beyond that, it’s important to remember that the hub is also the means to calling methods at the client end.
Starting at the server side, here’s the hub:
using Microsoft.AspNet.SignalR.Hubs;
namespace PopForums.Messaging
{
public class Topics : Hub
{
public void ListenTo(int topicID)
{
Groups.Add(Context.ConnectionId, topicID.ToString());
}
}
}
Have I mentioned how awesomely not complicated this is? The hub acts as the channel between the server and the client, and you’ll see how JavaScript calls the above method in a moment. Next, the broker class and its associated interface:
using Microsoft.AspNet.SignalR;
using Topic = PopForums.Models.Topic;
namespace PopForums.Messaging
{
public interface IBroker
{
void NotifyNewPosts(Topic topic, int lasPostID);
}
public class Broker : IBroker
{
public void NotifyNewPosts(Topic topic, int lasPostID)
{
var context = GlobalHost.ConnectionManager.GetHubContext<Topics>();
context.Clients.Group(topic.TopicID.ToString()).notifyNewPosts(lasPostID);
}
}
}
The NotifyNewPosts method uses the static GlobalHost.ConnectionManager.GetHubContext<Topics>() method to get a reference to the hub, and then makes a call to clients in the group matched by the topic ID. It’s calling the notifyNewPosts method on the client. The TopicService class, which handles the reply data from the MVC controller, has an instance of the broker new’d up by dependency injection, so it took literally one line of code in the reply action method to get things moving.
_broker.NotifyNewPosts(topic, post.PostID);
The JavaScript side of things wasn’t much harder. When you click the reply button (or quote button), the reply window opens up and fires up a connection to the hub:
var hub = $.connection.topics;
hub.client.notifyNewPosts = function (lastPostID) {
PopForums.setReplyMorePosts(lastPostID);
};
$.connection.hub.start().done(function () {
hub.server.listenTo(topicID);
});
The important part to look at here is the creation of the notifyNewPosts function. That’s the method that is called from the server in the Broker class above. Conversely, once the connection is done, the script calls the listenTo method on the server, letting it know that this particular connection is listening for new posts on this specific topic ID.
This whole experiment enables a lot of ideas that would make the forum more Facebook-like, letting you know when stuff is going on around you.