SignalR with MVC 5, Knockout and Web API

Tags: SignalR, MVC, ASP.NET, WebAPI, Knockout

There's an explosion of new javascript-based tools and frameworks right now. It's pretty damned exciting but also daunting. I've been working on so many projects (my day job as well as a second side job) that it's all I can do to keep up. Next stop for me is AngularJS. But that's for another day. Right now I want to walk through an ASP.NET MVC 5 solution that uses the new SignalR 2.0 to publish events to multiple subscribers.

Here's our scenario. We have a web page in our web application (called the "sender") sending new widgets to an internally hosted Web API controller. The API controller implements a SignalR IHub interface. It's our publisher. When it receives a new widget from the sender it will notify all connected clients (called "receivers") and send them the new widget. Here's the architecture:

If this is tl;dr for you young whippersnappers with zero attention spans then fork the repo containing the full working solution on github. If you want to get your hands dirty then follow these steps.

(1) Set up the solution by creating a new MVC 5 project in Visual Studio 2013. Be sure to check the Web API box to add core references for it. Also choose "No authentication" to keep things simple. I called my solution "SignalRDemo" but feel free to call it whatever you want.

(2) Add SignalR 2.0.2 and Knockout 3.0.0 nuget packages to the solution.

(3) Add the references for SignalR and Knockout to BundleConfig.cs:

bundles.Add(new ScriptBundle("~/bundles/signalr").Include(
    "~/Scripts/jquery.signalR-*"
));

bundles.Add(new ScriptBundle("~/bundles/knockout").Include(
    "~/Scripts/knockout-*"
));

(4) In _Layout.cshtml tell MVC to render the scripts:

@Scripts.Render("~/bundles/signalr")
@Scripts.Render("~/bundles/knockout")

Now might be a good time to build and run to make sure your scripts are referenced correctly. The next step is to bootstrap SignalR in the application. To do that add a Startup.cs class to the project root and add this code:

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(SignalRDemo.Startup))]
namespace SignalRDemo
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

I never looked very hard at SignalR 1.x but I know that this is a lot easier than what you had to do in previous versions. Ok, so we have SignalR bootstrapped but we don't have a hub. So let's create that now. I found a great abstract base class that implements IHub courtesy of Brad Wilson who used it at the 2012 Norwegian Developer's Conference (NDC). The video of his demo is hosted here. Create a Hubs folder and add a new class SignalRBase. Here's the code:

using System;
using System.Web.Http;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace SignalRDemo.Hubs
{
    public abstract class SignalRBase<THub> : ApiController where THub : IHub
    {
        private readonly Lazy<IHubContext> _hub = new Lazy<IHubContext>(
            () => GlobalHost.ConnectionManager.GetHubContext<THub>()
        );

        protected IHubContext Hub
        {
            get { return _hub.Value; }
        }
    }
}

If this is your first exposure to SignalR then it's worth reading the introduction. I don't plan to cover long polling or HTML 5 transports here. Esentially the SignalRBase superclass can be inherited by any subclass that implements a publishing hub in our application. Before I get to that let's add a concrete hub for our widgets. Add WidgetHub.cs to the Hubs folder:

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace SignalRDemo.Hubs
{
    [HubName("widgets")]
    public class WidgetHub : Hub { }
}

We next need to add a Web API controller. But it won't be an ordinary RESTful controller because it's also going to be our publisher. Add a new Web API controller called WidgetController.cs with this code:

using System.Net;
using System.Net.Http;
using SignalRDemo.Hubs;
using SignalRDemo.Models;

namespace SignalRDemo.Controllers
{
    public class WidgetController : SignalRBase<WidgetHub>
    {
        // POST api/<controller>
        public HttpResponseMessage Post(Widget item)
        {
            if (item == null)
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest);
            }

            // validate and add to database in a real app

            // notify all connected clients
            Hub.Clients.All.newWidget(item);

            // return the item inside of a 201 response
            return Request.CreateResponse(HttpStatusCode.Created, item);
        }
    }
}

Notice that it inherits from our SignalRBase superclass and uses generics to instantiate a new hub using the concrete WidgetHub class. You can see how useful this is since you could have a dozen Web API controllers in your application that use a handful of hubs to communicate with different clients in your application. The call to newWidget is dynamic so it gets resolved at runtime.

Add a new model called Widget.cs:

namespace SignalRDemo.Models
{
    public class Widget
    {
        public int ID { get; set; }
        public string Color { get; set; }
        public string Shape { get; set; }
    }
}

There are two views left to add (Sender.cshtml and Receiver.cshtml). In the HomeController create two methods for each view and add the views. For Sender.cshtml here is the HTML:

@{
    ViewBag.Title = "Sender";
}

<h2>Sender</h2>

<p>This view creates a new widget in the application.</p>

<div>
    Color: <input id="widget-color" type="text"/><br />
    Shape: <input id="widget-shape" type="text"/>
    <input type="button" id="send-button" value="Send" />
</div>

@section Scripts {

    <script>

        $('#send-button').click(function () {
            var id = getRandomInt(99);
            var color = $('#widget-color').val();
            var shape = $('#widget-shape').val();

            $.ajax({
                url: "/api/widget",
                data: { 'id': id, 'color': color, 'shape': shape },
                type: "POST"
            });

            // reset input fields
            $('#widget-shape').val('');
            $('#widget-color').val('').focus();        
        });

        // generate a random integer
        function getRandomInt(range) {
            return Math.floor(Math.random() * range);
        }

    </script>
}

The sender is a simple form to enter widgets into the system. When the user clicks the Send button we use jquery ajax to post the widget to the WidgetController. The WidgetController will invoke the dynamic call to newWidget() to notify all of the connected clients. That brings us to Receiver.cshtml:

@{
    ViewBag.Title = "Receiver";
}

<h2>Receiver</h2>

<p>This view subscribes to the WidgetHub</p>

<div>
    <span class="small-width bold">ID</span>
    <span class="medium-width bold">Color</span>
    <span class="medium-width bold">Shape</span>
</div>
<div data-bind="foreach: widgets">
    <div>
        <span data-bind="text: id" class="small-width"></span>
        <span data-bind="text: color" class="medium-width"></span>
        <span data-bind="text: shape" class="medium-width"></span>
    </div>
</div>

@section Scripts {
    <!-- Reference the autogenerated SignalR hub script -->
    <script src="~/signalr/hubs"></script>

    <script>

        var vm = null;

        $(function () {

            vm = new WidgetViewModel();
            ko.applyBindings(vm);

            var hub = $.connection.widgets;

            $.connection.hub.start();

            hub.client.newWidget = function (item) {
                var widget = new Widget(item.ID, item.Color, item.Shape);
                vm.addWidget(widget);
            };
        });

        function WidgetViewModel() {
            var self = this;
            self.widgets = ko.observableArray([]);

            self.addWidget = function (item) {
                self.widgets.push(item);
            };
        }

        var Widget = function (id, color, shape) {
            var self = this;
            self.id = ko.observable(id);
            self.color = ko.observable(color);
            self.shape = ko.observable(shape);
        };

    </script>
}

Notice the Knockout bindings. Pretty straightforward. The interesting thing here is how the client instantiates a local hub connection to the widgets hub. Why widgets? If look at the WidgetHub class that's the name we gave it. Notice also that the hub plugs into a callback function newWidget() to receive a new widget once the Web API WidgetController class publishes it. Upon receiving a new widget it's just a simple matter of adding it to the observableArray so that the DOM updates.

To try it out run the solution. Then copy the URL from the browser address bar and paste it into 2 or 3 other new browser window instances. Point one of the browsers to the Sender page and the others to the Receiver page. Then create a new widget and watch it propagate through the system.