This is a preview-quality chapter of our continuously deployed eBook on AngularJS for .NET developers. You can read more about the project at http://henriquat.re. You can also follow us on twitter (Project: @henriquatreJS, Authors: @ingorammer and @christianweyer)

 

Pushing Data: Integrating with ASP.NET SignalR Hubs

In modern applications the end users want to get their data. They want it now, they want it up-to date. In fact it does not matter whether these are pure web application, native desktop installations or mobile apps: everybody wants his data now!

For .NET-minded developers there are a numbers of options to implement near-real-time push style communication from the server/the services to the clients/consumers. You can choose plain HTTP or the super-new WebSockets features available in .NET 4.5 together with Windows 8 and Windows Server 2012. But the coolest and increasingly popular approach is to use a new framework: ASP.NET SignalR.

While it is not intended- and surely beyond the scope of this ebook - to give an introduction or even deep dive into SignalR, we need to have a look at some concepts and code in order to realize a smooth integration of SignalR and AngularJS.

The final goal of this chapter is to have an AngularJS-style integration of calling and listening to server-side SignalR push services.

Near-Real-Time Push Communication With ASP.NET SignalR

As already said, SignalR is the latest and newest kid on the Microsoft server-side technology stack block. In easy words, it enables developers to realize push style communication in near realtime fashion without having to worry about most of the headache-bringing details.

Which transport protocol mechanism should I use? WebSockets, Server-Sent Events, Forever Frame, or just Long Polling? Who tells me what which browser (and server!) supports? And do I really always have to reinvent the asynchronous coding for especially the server side in order to get the maximum performance and throughput out of my system?

SignalR can take care of all of this!

ASP.NET's SignalR offers two abstractions and programming models:

  • Persistent connections
  • Hubs

For the matter of this book chapter we will focus on hubs which are the 80/20 way in SignalR, anyway.

For a more thorough introduction to implement push services with SignalR hubs please head over to our friends at Pluralsight for their online on-demand course on SignalR.

A Simple SignalR Hub

Our server-side hub for demonstration in this chapter will be a server time emitting push service.

It has one method which can be called as the inbound API. And we will add a background worker in ASP.NET which will periodically push the server time every 5 seconds to all connected clients. For the sake of clarification that hubs do not really need to have both, an inbound and outbound API we will use two different hubs:

  • one for calling the server time from the client (kind of a Web API replacement, but based on the hubs protocol) - aka inbound API.
  • another one for using the hub context to call into connected clients - aka outbound API.

using Microsoft.AspNet.SignalR;
using System;

namespace Henriquatre.Integration.SignalR
{
    public class ServerTimeHub : Hub
    {
        public string GetServerTime()
        {
            return DateTime.UtcNow.ToString();
        }
    }

    public class ClientPushHub : Hub
    {
    }
}

Couldn't be simpler, eh? Yes, sometimes .NET is really beautiful. In addition we will also have code on the server which uses the hub to call into clients through this hub, but from outside the hub itself. Huh? Sounds weird? It isn't, trust us. We will implement this with a background worker logic running in ASP.NET.

Now, let's quickly head over to the background worker.

Scheduling Frequent Pushes on the Server

The official and recommended approach to run scheduled background work in ASP.NET (if there is no other, more robust way, like having an explicit Windows service or maybe a Windows Azure worker role) is to implement an IRegisteredObject with the ASP.NET runtime and hook it up at application start.

In the end it can be a simple as this:

using Microsoft.AspNet.SignalR;
using System;
using System.Threading;
using System.Web.Hosting;

namespace Henriquatre.Integration.SignalR
{
    public class BackgroundServerTimeTimer : IRegisteredObject
    {
        private Timer taskTimer;
        private IHubContext hub;

        public BackgroundServerTimeTimer()
        {
            HostingEnvironment.RegisterObject(this);

            hub = GlobalHost.ConnectionManager.GetHubContext<ClientPushHub>();
            
            taskTimer = new Timer(OnTimerElapsed, null, 
                TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5));
        }

        private void OnTimerElapsed(object sender)
        {
            hub.Clients.All.serverTime(DateTime.UtcNow.ToString());
        }

        public void Stop(bool immediate)
        {
            taskTimer.Dispose();

            HostingEnvironment.UnregisterObject(this);
        }
    }
}

Inside of the constructor we get a reference to the hub context for our ClientsPushHub. This reference is then used in the timer's event to call all registered clients and actually call the serverTime 'method' on the client side.

In global.asax.cs we can simply create and set up the registered objects (we have another in place here for pushing performance data, see below in this chapter) and get our hubs pipeline working by adding the hub routes to the routes table. In this case we enable CORS as the SignalR services are located on a different server than the client-side HTML and JavaScript application.

using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.AspNet.SignalR;

namespace Henriquatre.Integration.SignalR
{
    public class MvcApplication : System.Web.HttpApplication
    {
        private BackgroundServerTimeTimer bstt;
        private BackgroundPerformanceDataTimer bpdt;

        protected void Application_Start()
        {
            bstt = new BackgroundServerTimeTimer();
            bpdt = new BackgroundPerformanceDataTimer();

            RouteTable.Routes.MapHubs(new HubConfiguration 
                { EnableCrossDomain = true });

            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
    }
}

Speaking of the JavaScript clients, let's have a quick look at how to build a JS-based application with the Microsoft-provided JavaScript APIs.

SignalR JavaScript Client APIs

The current version (1.1 beta as of writing) provides two jQuery-based JavaScript APIs:

  • with JS proxy: the client uses a dynamically generated JS file as a proxy description for the hubs ('SignalR-WSDL', if you like).
  • without JS proxy: there is no static metadata involved at all and we wire up events and do method calls based on event and method names.

For a smooth integration between SignalR's JavaScript and AngularJS we are going to choose the no proxy route (no pun intended!).

An AngularJS Service for Integrating Hubs

With the above in mind we can write an AngularJS service that wraps and encapsulates the SignalR communication.

The following code shows the fully functional service. We are actually creating a kind of factory which the user of the service can then do different things with by working on the internally created runtime proxy object:

  • register local JavaScript methods which can be called by a server-side SignalR hub (local events, if you will) - this happens through the on method.
  • unregister a local JavaScript method from the runtime proxy by using off.
  • calling server-side hub methods by using the invoke function.

For convenience, we are also exposing the SignalR hub connection in order to get relevant data like e.g. the connection ID.

'use strict';

app.factory('signalRHubProxy', ['$rootScope', 'signalRServer', 
    function ($rootScope, signalRServer) {
        function signalRHubProxyFactory(serverUrl, hubName, startOptions) {
            var connection = $.hubConnection(signalRServer);
            var proxy = connection.createHubProxy(hubName);
            connection.start(startOptions).done(function () { });
            
            return {
                on: function (eventName, callback) {
                    proxy.on(eventName, function (result) {
                        $rootScope.$apply(function () {
                            if (callback) {
                                callback(result);
                            }
                        });
                    });
                },
                off: function (eventName, callback) {
                    proxy.off(eventName, function (result) {
                        $rootScope.$apply(function () {
                            if (callback) {
                                callback(result);
                            }
                        });
                    });
                },
                invoke: function (methodName, callback) {
                    proxy.invoke(methodName)
                        .done(function (result) {
                            $rootScope.$apply(function () {
                                if (callback) {
                                    callback(result);
                                }
                            });
                        });
                },
                connection: connection
            };
        };

        return signalRHubProxyFactory;    
}]);

The sample controller below illustrates how to use the SignalR AngularJS service. First, we get a reference to that 'factory' object by passing in the base URL of the SignalR hubs. This is essential when using the hubs in a cross-domain scenario. Optionally we can also pass the documented SignalR client configuration options.

From there on it is straight-forward to hook up client events and invoke/call methods on the server.

function ServerTimeController($scope, signalRHubProxy) {
    var clientPushHubProxy = signalRHubProxy(
        signalRHubProxy.defaultServer, 'clientPushHub', 
            { logging: true });
    var serverTimeHubProxy = signalRHubProxy(
        signalRHubProxy.defaultServer, 'serverTimeHub');

    clientPushHubProxy.on('serverTime', function (data) {
        $scope.currentServerTime = data;
        var x = clientPushHubProxy.connection.id;
    });
    
    $scope.getServerTime = function () {
        serverTimeHubProxy.invoke('getServerTime', function (data) {
            $scope.currentServerTimeManually = data;
        });
    };
};

Nobody should complain about that programming model, right?! But you are surely complaining… well, maybe you are just asking how this integration works?

Cycles, Heartbeats - $apply

But how does this thing above work, dude?

AngularJS internally works with cycles when it comes to execute data binding and to determine when values of the model have changed. One of the cycles is the digesting cycle where Angular checks for model changes by processing and evaluating all registered watch expressions. But if model changes happen outside of the AngularJS world - in our case we get a callback executed externally, not happening in any controller or AngularJS artifact - then we need to instruct Angular that the model actually has changed.

This is what $apply is for: available on the scope, it triggers (or forces) the digesting cycle by calling $digest internally (API Reference: ng.$rootScope.Scope) and this is what we need to use in our SignalR-wrapping service.

on: function (eventName, callback) {
    proxy.on(eventName, function (result) {
       $rootScope.$apply(function () {
          if (callback) {
             callback(result);
          }
       });
    });
}

Some of you already playing around with AngularJS may ask: "OK, cool - but why is it not needed to use $apply with my $http services?" Easy: the AngularJS team followed the good practices and already implemented the wrapping for us.

In Action!

Last but not least we are showing you a running embedded example of the AngularJS-SignalR integration. This does not only use the already shown and discussed two hubs for the server time but a new one which frequently pushes current performance counter data (CPU values, in this case) to the connected clients.

Note: The server-side hubs run in a separate Windows Azure Web Sites deployment.

By the way - the chart is visualized by SmoothieChart - and we also included a very simple Angular directive for smoothie.

Now enjoy.

<div ng-app="signalRIntegrationApp">
    <div ng-controller="ServerTimeController">
        <div>
            <h3>Time from server (being pushed every 5 seconds):</h3>
            <h4>{{currentServerTime}}</h4>
        </div>

        <div>
            <button ng-click="getServerTime()">Get time from server</button>

            <div>
                <h3>Manual time from server:</h3>
                <h4>{{currentServerTimeManually}}</h4>
            </div>
        </div>
    </div>

    <div ng-controller="PerformanceDataController">
        <h3>Overall CPU time:</h3>
        <div>
            <smoothie-chart data="cpuData" />
        </div>
    </div>
</div>
<script src="http://henriquat.re/demoinfrastructure/jquery-1.9.1.min.js"></script>
<script src="http://henriquat.re/demoinfrastructure/jquery.signalR-1.1.0-beta1.min.js" ></script>
<script src="http://henriquat.re/demoinfrastructure/smoothie.js"></script>

Summarizing

This chapter showed you how to use AngularJS' concept of services to encapsulate both, communication aspects and 3rd party library integration, by presenting a way how to leverage ASP.NET SignalR in your Angular applications to get easy-to-implement and easy-to-use near realtime push communications.

You can download the entire Visual Studio 2012 solution here. VS 2012 SLN

Mission complete. Now go and push your data.

 

This was a preview-quality chapter of our continuously deployed eBook on AngularJS for .NET developers. If you enjoyed this chapter, you can read more about the project at http://henriquat.re. You can also follow us on twitter (Project: @henriquatreJS, Authors: @ingorammer and @christianweyer)