I decided that I'd be really interested to find out if it would be possible to use .NET to produce a Facebook-style news feed that automatically refreshes as soon as something is pushed to the list. Podio does a really good job of this too. As soon as a user posts an update on a story, the change is immediately pushed to all clients complete with a snazzy "knock-knock" sound effect.
There are a few ways of accomplishing this using standard AJAX. The first is long-polling, which maintains an open connection waiting for the server to send something when it's ready. The second way of doing this is using the Javascript setInterval function to poll the server and see if anything has changed every x number of seconds.
However, I wanted a Websockets-style solution that would manage connections better. I did a bit of reading around the subject, and arrived at SignalR.
The clever bit about SignalR is that it provides all the support for this sort of connection for you. It starts by trying a WebSockets connection, and if your browser (or the webserver for that matter) doesn't support it, it picks a different method of transport until it finds something that fits. For Chrome and IIS 8 it would be WebSockets, and for any Internet Explorer version, it uses Forever Frame. You can read more about that on this StackOverflow answer from one of the authors.
My plan was this. I would produce a simple MVC application with a standard create form with two fields. Name and update type (either news or event). When the MVC submission was complete, the update would appear in all connected browsers, almost like a one-way multicast chat program, but over HTTP instead of a standard socket.
I then wanted a bit of JQuery in place that would push the list down and fade my new update in at the top of the list, because the list is ordered by publish date.
I created a blank MVC project, and created a controller class with skeleton actions for index and create. My view contained references to JQuery, JQuery UI, the SignalR Javascript includes, and the standard rendering of the existing list.
To prevent any confusion of the point of the project, this example doesn't connect to a database. I create a list of existing news and event updates in global.asax.cs, and store them in the HttpContext.Application object.
I then needed to create a Hub class that my client would connect to in order to receive updates. If you want the client to fire methods on the server-side, you can create them in this hub class. In my case however, I'm just pushing content from the server to the client, and this is done later on in my controller class.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Web; | |
using SignalR.Hubs; | |
namespace LiveUpdate.Hubs | |
{ | |
/// <summary> | |
/// Skeleton hub class used to establish connections. | |
/// We could pop methods in here that could be called directly by the client. | |
/// </summary> | |
[HubName("updateFeed")] | |
public class LiveUpdateHub : Hub | |
{ | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Broadcasts the update to all connected clients. I've done this directly in the controller to | |
/// demonstrate server -> client pushing, rather than client -> client pushing. | |
/// </summary> | |
/// <param name="updateItem">The update item.</param> | |
internal static void BroadcastUpdate(Update updateItem) | |
{ | |
// Fetch the hub's context to broadcast | |
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<LiveUpdate.Hubs.LiveUpdateHub>(); | |
// Call the hub's feedUpdated method with our news / event item. | |
context.Clients.feedUpdated(updateItem); | |
} |
The idea of a lot of SignalR examples is to push client events to the server, which then distributes it to all clients. I didn't want client-side code muddying the waters of submission of simple data, because in the future, data may change in a different way.
For example, we may want to create a delegate method server-side that performs some functionality and then pushes updates to the clients, rather than waiting for client input. My example is broadcasting a server-side event to all connected clients. This is all happening in BroadcastUpdate using .NET 4.0 dynamic variables to call client functionality.
The last step was to implement some Javascript that would listen on a connection for a call from the server to a client-side method. When this response is sent over, we get a serialized JSON object containing our update. We then do a bit of nifty JQuery to make it fade into appearance at the top of the list.
The Javascript looks like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <reference path="jquery-1.7.2.js" /> | |
/// <reference path="jquery-ui-1.8.20.js" /> | |
/// <reference path="jquery.signalR-0.5.2.js" /> | |
$(function () { | |
// Establish a connection to the updateFeed hub | |
var hub = $.connection.updateFeed; | |
// Extend the object with our feedUpdated method held within the updateFeed hub | |
$.extend(hub, { | |
feedUpdated: function (data) { | |
// Container for newItem | |
var blankDiv = $("<div class='itemContainer'></div>"); | |
// Holds the update | |
var newItem = $("<div class='" + data.UpdateType.toLowerCase() + "_item'><span>" + data.Name + "</span></div>"); | |
// Hide our blank div (that will contain the new update), and our new item itself. | |
// The blank div will slide down, and our newItem will fade into existence. | |
blankDiv.hide(); | |
newItem.hide(); | |
// Insert the update at the top of the list, as it's sorted in descending order by publish date. | |
$("div#container div:first").before(blankDiv); | |
blankDiv.html(newItem); | |
// Slide down the blank div (it has a fixed height in CSS), and then fade the new update in. | |
blankDiv.slideDown(500, null, function () { | |
newItem.fadeIn(2000) | |
}); | |
} | |
}); | |
// Kick off the connection. | |
$.connection.hub.start(); | |
}); |
It's really best to get the source code and see the demo in action to really understand how it all fits together, so I've uploaded the project to a repository on GitHub so that you can get it running yourselves.
I understood much more about how to use SignalR in this video about SignalR.