Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.
I think we all will agree that Web Sockets is pretty cool. The type of bidirectional communication it affords opens up a world of opportunity. Unfortunately, support for Web Sockets is still spotty. So, while Web Sockets is certainly a glimpse of the future, it may not seem like a viable option for the systems you are building today. If only we had some way to take advantage of Web Sockets when it is available, but fall back to other options when it isn’t.
As I’m sure you have guessed, this is certainly achievable, and one option for doing so is built into a set of libraries named SignalR. SignalR is something that two Microsoft employees started as an open source project that was later adopted by Microsoft as an officially sponsored project. SignalR makes it easy to build real-time solutions using asynchronous signaling approaches. SignalR makes it possible for us to build modern solutions today that will automatically take advantage of Web Sockets when available.
So what does SignalR do when Web Sockets support isn’t available? It checks for a few different options, and uses the one that is best suited to the task at hand. Web Sockets is checked first. If that isn’t supported then Server-Sent Events, Forever Frame, and Long Polling are checked.
SignalR isn’t the only game in town when it comes to falling back to other techniques such as Forever Frame and Long Polling. Socket.IO and NowJS are a few options that do this as well. However, SignalR goes about it in an easier way and is specifically designed to play well with ASP.NET.
Two primary options are available for building the SignalR server-side aspects. The first is a persistent connection and the second is a hub. The persistent connection option is pretty similar to what we’ve seen in the Fleck and IIS 8 web socket server implementations provided in this chapter.
As you’ll see, it doesn’t take much code to get this set up. You
start by installing the SignalR NuGet
package. After that, it’s a simple matter of adding a custom route map
and implementing a class that derives from PersistentConnection. A snippet from a Global.fs file with the new code
emphasized is shown in the following example:
typeGlobal()=inheritSystem.Web.HttpApplication()staticmemberRegisterRoutes(routes:RouteCollection)=routes.IgnoreRoute("{resource}.axd/{*pathInfo}")routes.MapRoute("Default","{controller}/{action}/{id}",{controller="Home";action="Index"id=UrlParameter.Optional})memberthis.Start()=RouteTable.Routes .MapConnection<ChartServer>("chartserver", "chartserver/{*operation}") |> ignoreAreaRegistration.RegisterAllAreas()Global.RegisterRoutes(RouteTable.Routes)
The new class that inherits PersistentConnection is shown in the next
example. As I mentioned, it’s not that much different from the
previously shown IIS 8 web socket server examples. Here we’re simply
overriding the OnReceivedAsync method
rather than the OnMessage
method:
moduleSignalRExampleopenSystemopenSignalRopenNewtonsoft.Json// Code from previous examples removed for brevitytypeChartServer()=inheritPersistentConnection()overridex.OnReceivedAsync(request,connectionId,data)=votesAgent.PostAndReply(funreply->Message.Vote(data,reply))|>Seq.map(funv->{language=fstv;count=sndv})|>JsonConvert.SerializeObject|>base.Connection.Broadcast
The client-side aspects of SignalR change slightly depending on
whether the client is using JavaScript or F# as well as whether the
server is using PersistentConnection
or Hub. Additionally, the JavaScript
option is a little bit different from the previously seen web socket
client-related JavaScript. This difference is partially due to the
jQuery plug-in that helps SignalR do its
magic. Here is a JavaScript example for the PersistentConnection that
we built in the earlier example:
/* Some code removed for brevity */varconnection=$.connection("/chartserver");connection.received(function(data){updateChart($.parseJSON(data));});$('#vote').click(function(event){varvote=$('input:radio[name=langOption]:checked');if(vote.length){connection.send(vote.val());};$pages.hide();$("#results").toggle();event.preventDefault();});connection.start();
Using the client and server together results in the same effect we achieved in the previous web socket examples. The output looks just like Figure 4-2.
What if you want to connect to a SignalR server from F# instead of JavaScript? That’s no problem at all. The next example shows an F# console application that can cast a vote for a favorite programming language.
To build this, create a new F# console application, install the
SignalR.Client NuGet package, and add
code such as the following:
moduleSignalRExampleopenSystemopenSignalR.Clientletconnection=Connection"http://localhost:2920/chartserver"connection.Start().Wait()connection.Send"F#"|>Async.AwaitIAsyncResult|>Async.Ignore|>ignoreconnection.Stop()|>ignoreprintfn"Vote cast for F#"Console.ReadLine()|>ignore
As I mentioned previously, SignalR provides another option, which
is known as a Hub. This option
provides a higher-level abstraction over PersistentConnection. This abstraction makes
it possible for the server to call named JavaScript functions. Let’s
look at an example.
The next example provides a self-hosted SignalR Hub. It’s an F# console application that has
had the SignalR.Hosting.Self
package installed. To set up the hub, you create a class that inherits
from Hub. You then add the methods
you wish to be available for calls from clients. To call functions
declared in JavaScript, you can use the Clients dynamic object followed by the name
of the desired function. This code is emphasized in the following
example:
// Code from previous examples removed for brevitytypeChartHub()=inheritHub()memberx.Send(data:string)=letresult=votesAgent.PostAndReply(funreply->Message.Vote(data,reply))|>Seq.map(funv->{language=fstv;count=sndv})trybase.Clients?updateChart(result)with|ex->printfn"%s"ex.Messageletserver=Server"http://*:8181/"server.MapHubs()|>ignoreserver.Start()printfn"Now listening on port 8181"Console.ReadLine()|>ignore
One thing you may have noticed in the preceding example is the
use of the ? operator when working
with the dynamic Clients object. F#
doesn’t provide the exact same dynamic object option that is provided
in C#. Instead, it provides a more powerful option that allows you to
implement whatever dynamic functionality you require. This option is
provided in the form of dynamic lookup
operators.
Dynamic lookup operators can provide the same functionality as
what is offered in C#, though they’re not restricted to that
functionality only. You can customize the implementation of dynamic
lookup operators in whatever way you see fit, allowing power beyond
that of its cousin language. One implementation that has gained
popularity is available via the ImpromptuInterface.FSharp library, which can
be installed with a NuGet package ID of the same name. The Hub example uses this library.
The JavaScript code changes slightly when using a Hub as well. Here’s the example we have been
reviewing, with the changes that are required to interact with a
Hub emphasized:
$.connection.hub.url = 'http://localhost:8181/signalr' var chartHub = $.connection.chartHub; chartHub.updateChart=function(data){updateChart(data);};$('#vote').click(function(event){varvote=$('input:radio[name=langOption]:checked');if(vote.length){chartHub.send(vote.val())}$pages.hide();$("#results").toggle();event.preventDefault();});$.connection.hub.start();
One other key thing we have to do to make this work is to add a few script references such as the following:
<scriptsrc="@Url.Content("~/Scripts/jquery.signalR-0.5.2.min.js")"></script><scriptsrc="http://localhost:8181/signalr/hubs"type="text/javascript"></script>