December 23, 2015 · SignalR F# Owin

Getting Started with SignalR using F# and OWIN

SignalR allows us to easily push messages back and forth between a client (usually a website) and server using websockets. All of the pain of creating a connection, keeping the connection alive, reconnecting, serialising and deserialising messages, plus lots, lots more is taken care of for you.

In this post we're going to create a SignalR server and push some messages back and forth from a website. You can see the completed source at github.com/troykershaw/signalr-fsharp-getting-started.

Note: This will probably only run on Windows.

Get the project set up

Hello, Paket

We're going to use Paket to download and manage our dependencies. Compared to nuget Paket requires a little effort to set up for a new project but it's totally worth it. Here's how to do it, basically pulled straight from Paket's Getting Started page

source https://nuget.org/api/v2

nuget Microsoft.AspNet.SignalR.SelfHost
nuget Microsoft.Owin.Cors
nuget Microsoft.Owin.StaticFiles
nuget FSharp.Interop.Dynamic
Microsoft.AspNet.SignalR.SelfHost
Microsoft.Owin.Cors
Microsoft.Owin.StaticFiles
FSharp.Interop.Dynamic

Now you've got everything you need for the rest of this blog post.

Let's get the server running

SignalR is part of the ASP.NET family and runs nicely in a standalone OWIN server, so let's do that.

module Signalr

open Owin
open FSharp.Interop.Dynamic
open Microsoft.AspNet.SignalR
open Microsoft.Owin.Hosting
open Microsoft.Owin.Cors
open Microsoft.AspNet.SignalR.Hubs

type Server (host:string) =
    let startup (a:IAppBuilder) =
        a.UseCors(CorsOptions.AllowAll) |> ignore
        a.MapSignalR() |> ignore

    do
        WebApp.Start(host, startup) |> ignore
        printfn "Signalr server running on %s" host

But of course that won't do anything yet.

[<EntryPoint>]
let main _ =
    let signalr = Signalr.Server "http://localhost:7777"

    System.Console.ReadLine() |> ignore
    0

Now you can run it, and get a loose confirmation that it worked because it printed something to the Console window.

What's happening is Program.fs creates an instance of our SignalR server and then doesn't exit because it's waiting for you to press Enter. Signalr.fs creates a startup function that enables CORS and enables Signalr, then we start the OWIN server using that configuration.

Send some messages to a web page

To actually be useful let's send a message to a web page. We, of course, first need to create a web page and some way of serving that page. We could just tack it on to our current SignalR server but because OWIN servers are so easy to write let's create a new one.

module Static

open Owin
open Microsoft.Owin.Hosting

type Server (host:string) =
    let startup (a:IAppBuilder) =
        a.UseFileServer(true) |> ignore

    do
        WebApp.Start(host, startup) |> ignore
        printfn "Static server running on %s" host

We now have a server that serves static files. Let's start it.

<!DOCTYPE html>
<html>
<body>
    <pre id="message"></pre>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.4.min.js"></script>
    <script src="http://ajax.aspnetcdn.com/ajax/signalr/jquery.signalr-2.2.0.min.js"></script>
    <script src="http://localhost:7777/signalr/hubs"></script>

    <script type="text/javascript">
        $(function () {
            // set our hub url
            $.connection.hub.url = "http://localhost:7777/signalr";

            // get our hub
            var hub = $.connection.myFirstHub;

            // function that the server will call
            hub.client.messageToTheClient = function (message) {
                var json = JSON.stringify(message, null, 2);

                // add the message to the page
                $('#message').text(json);
            }

            // connect to the server
            $.connection.hub.start()
                .done(function () {
                    // we've connected, so let's send a message!
                    hub.server.messageToTheServer("Hello from the client!");
                });
        });
    </script>
</body>
</html>

When you go to http://localhost:8777/index.html you'll get a blank web page! Yay!

MyFirstHub

The SignalR client and server relationship communicates over 'hubs', so let's create one of those.

[<HubName("myFirstHub")>]
type MyFirstHub () =
    inherit Hub ()

    member x.MessageToTheServer message =
        printfn "Someone sent us a message! - %s" message

The MessageToTheServer method is what SignalR clients call. If you run our app now and refresh the web page the web page will send the message "Hello from the client!" to our server which you can confirm is happening because it prints it to the console.

Sending messages

Sending messages to our clients is quite easy, we don't even need to update our hub. What we will do, however, is create a method that consumers of our server can call to send a message.

let clients = GlobalHost.ConnectionManager.GetHubContext<MyFirstHub>().Clients

This gives us an easy way to get to our clients.

member x.Send message = clients.All?messageToTheClient message

In the Send method we simply push the message to all clients (clients.All) and call the function messageToTheClient on the client.

Let's call signalr.Send in Program.fs. Actually, let's send a message every second.

let rec loop message =
    async {
        signalr.Send message
        do! Async.Sleep 1000
        return! loop message
    }

"The server says hello!"
|> loop
|> Async.RunSynchronously

Run our app, refresh the page and the server message appears on the screen. Magic!

Debugging in Chrome

It's easy to see what's being sent to the client from the server in Chrome.

Go to our webpage, open dev tools (Ctrl + Shift + I), select the 'Network' tab and select the 'WS' filter.

Reload the page and you'll see something pop up under 'Name' that reads similar to 'connect?transport=webSockets&clientProtocol=1.5&connectionToken=...'. Select it and then press 'Frames' just to the right of it.

Sure enough, at the top of the list we see the message the client sent and every second after that we see a new message from the server. The {} messages are a 10 second heart beat.

Sending something a little more interesting

Sending a string is fun but there's not a lot we can do with that. Let's send something a little more complex.

We've had too much excitement up until now so I'll bring the mood down a bit by creating a boring Customer type.

module Customer

type Customer =
    {
        Name : string
        Phone : string
        Email : string
        Address : Address
    }

and Address =
    {
        Street : string
        Suburb : string
        City : string
        ZipCode : string
        Planet : string
    }
{
    Name = "Philip J. Fry"
    Phone = "201 289 654"
    Email = "fry@planetexpress.com"
    Address =
        {
            Street = "Robot Arms Apartments, Apt 00100100"
            Suburb = "Manhattan"
            City = "New New York"
            ZipCode = "10007"
            Planet = "Earth"
        }
}

Now when we run the app our web page gets the customer message as nice looking json:

{
  "Name": "Philip J. Fry",
  "Phone": "201 289 654",
  "Email": "fry@planetexpress.com",
  "Address": {
    "Street": "Robot Arms Apartments, Apt 00100100",
    "Suburb": "Manhattan",
    "City": "New New York",
    "ZipCode": "10007",
    "Planet": "Earth"
  }
}

Sending F# things

What happens when we send F# specific things though?

module Customer

type Customer =
    {
        Name : string
        Phone : Phone option
        Email : Email option
        Address : Address option
    }

and Phone = Phone of string

and Email = Email of string

and Address =
    {
        Street : string
        Suburb : string
        City : string
        ZipCode : ZipCode
        Planet : string
    }

and ZipCode = ZipCode of string
{
    Name = "Philip J. Fry"
    Phone = Phone "201 289 654" |> Some
    Email = Email "fry@planetexpress.com" |> Some
    Address =
        {
            Street = "Robot Arms Apartments, Apt 00100100"
            Suburb = "Manhattan"
            City = "New New York"
            ZipCode = ZipCode "10007"
            Planet = "Earth"
        } |> Some
}

Run everything again.

It worked! Although the json is really, really ugly.

{
  "Name": "Philip J. Fry",
  "Phone": {
    "Case": "Some",
    "Fields": [
      {
        "Case": "Phone",
        "Fields": [
          "201 289 654"
        ]
      }
    ]
  },
  "Email": {
    "Case": "Some",
    "Fields": [
      {
        "Case": "Email",
        "Fields": [
          "fry@planetexpress.com"
        ]
      }
    ]
  },
  "Address": {
    "Case": "Some",
    "Fields": [
      {
        "Street": "Robot Arms Apartments, Apt 00100100",
        "Suburb": "Manhattan",
        "City": "New New York",
        "ZipCode": {
          "Case": "ZipCode",
          "Fields": [
            "10007"
          ]
        },
        "Planet": "Earth"
      }
    ]
  }
}

Look at all of those Cases and Fields. Yuck.

The good news and the bad news

The good news is that we can serialise and deserialise our Customer record into really clean json. The bad news is that you're going to have to wait for the next installment.

Remember you can check out the source for this post at github.com/troykershaw/signalr-fsharp-getting-started.

Merry Christmas everyone!

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket