My very own Taskmanager in HTML 5, SignalR, Nancy and AngularJS

image

This is part 2 of this blog post here and I really encourage you to read it before continuing reading this blog post.

Anyway the last thing to add to my small Taskmanager was AngularJS. I started to think about how to get SignalR and AngularJS working together and I got some real good pointers from the post “A Better Way of Using ASP.NET SignalR With Angular JS”.

I had another challenge with my existing code like the following statement in the JavaScript:

var chart = new Chart(document.getElementById(“canvas”).getContext(“2d”)).Line(lineChartData, options);

I could just move this code into my AngularJS controller but that seemed very ugly. So I found someone that already wrapped ChartJS in AngularJS directives into a small JavaScript library called Angles. So now when I was done reading others blog posts I felt ready to move on.

The AngularJS magic

Firstly I just pulled down AngularJS.Core and bootstrap NuGet packages.

  • Install-Package AngularJS.Core
  • Install-Package bootstrap

Then I added my own app.js JavaScript file that would contain my small application.

image

So far everything is pretty straightforward don’t you think?

Creating the SignalR connection in AngularJS

From the the post mentioned earlier I concluded that I needed to create a AngularJS service to hook up all my SignalR magic. The reason for this is that AngularJS services are singletons and that sounds perfect for this implementation.

var taskManagerApp = angular.module('taskManagerApp', ["angles"])
    .service('signalRSvc', function ($rootScope) {
        var initialize = function () {
            var cpuHub = $.connection.cpuHub;

            cpuHub.client.cpuInfo = function (machineName, cpu) {
                $rootScope.$emit("cpuInfo", machineName, cpu);
            }

            $.connection.hub.start();
        };

        return {
            initialize: initialize
        };
    })

Some important points in the code snippet above:

  • [“angles”], tells my AngularJS app to use the Angles library
  • .service, tells my AngularJS app that the next thing is a service named ‘signalRSvc’
  • the $rootScope.$emit, tells my AngularJS app to broadcast the current machineName and cpu to my controller.

The AngularJS Controller

Lets move on to the Controller code, this is where we control our view which is my index.html.

.controller('ChartController', function ($scope, signalRSvc, $rootScope) {
        $scope.machineName = "localhost";
        $scope.cpuChartLabel = "Total % Processor Time";
        $scope.lineChartData = {
            labels: [""],
            datasets: [
                {
                    fillColor: "rgba(241,246,250,0.5)",
                    strokeColor: "rgba(17,125,187,1)",
                    pointColor: "rgba(17,125,187,1)",
                    pointStrokeColor: "#fff",
                    data: [0]
                }
            ]
        };

        $scope.options = {

            //Boolean - If we show the scale above the chart data			
            scaleOverlay: false,

            //Boolean - If we want to override with a hard coded scale
            scaleOverride: true,

            //** Required if scaleOverride is true **
            //Number - The number of steps in a hard coded scale
            scaleSteps: 10,
            //Number - The value jump in the hard coded scale
            scaleStepWidth: 10,
            //Number - The scale starting value
            scaleStartValue: 0,

            //String - Colour of the scale line	
            scaleLineColor: "rgba(0,0,0,.1)",

            //Number - Pixel width of the scale line	
            scaleLineWidth: 1,

            //Boolean - Whether to show labels on the scale	
            scaleShowLabels: true,

            //Interpolated JS string - can access value
            scaleLabel: "<%=value%>",

            //String - Scale label font declaration for the scale label
            scaleFontFamily: "'Arial'",

            //Number - Scale label font size in pixels	
            scaleFontSize: 12,

            //String - Scale label font weight style	
            scaleFontStyle: "normal",

            //String - Scale label font colour	
            scaleFontColor: "#666",

            ///Boolean - Whether grid lines are shown across the chart
            scaleShowGridLines: true,

            //String - Colour of the grid lines
            scaleGridLineColor: "rgba(0,0,0,.05)",

            //Boolean - Whether the line is curved between points
            bezierCurve: false,

            //Boolean - Whether to show a dot for each point
            pointDot: false,

            //Boolean - Whether to animate the chart
            animation: false,
        };

        signalRSvc.initialize();

        var updateChartData = function (machineName, cpu) {
            if ($scope.lineChartData.labels.length &gt; 20) {
                $scope.lineChartData.labels.shift();
            }

            $scope.lineChartData.labels.push("");

            if ($scope.lineChartData.datasets[0].data.length &gt; 20) {
                $scope.lineChartData.datasets[0].data.shift();
            }

            $scope.lineChartData.datasets[0].data.push(cpu);
        }

        $scope.$parent.$on("cpuInfo", function (e, machineName, cpu) {
            $scope.$apply(function () {                
                $scope.machineName = machineName;
                updateChartData(machineName, cpu)
            });
        });
    });

Some important points in the code snippet above:

  • .controller, tells my AngularJS app that the next thing is a controller named ‘ChartController’ and it uses a function that takes our service signalRSvc as a parameter.
  • signalRSvc.initialize();, tells my AngularJS app to initiate the SignalR connection.
  • $scope.$parent.$on(“cpuInfo”, function (e, machineName, cpu), tells my AngularJS to listen to calls from “cpuInfo” and this will then call into the updateChartData

The view binding it all together

Having done all the hard lifting from index.html to app.js file we end up with a very simple index.html like so

    <div class="container">
        <div ng-app="taskManagerApp">
            <div class="jumbotron" ng-controller="ChartController">
                <h1>{{machineName}}</h1>
                <div class="span" %>
                    <h3>{{cpuChartLabel}}</h3>
                    <canvas id="lineChart" data="lineChartData" options="options" linechart></canvas>
                </div>
            </div>
        </div>

        <!-- Placed at the end of the document so the pages load faster -->
        <script src="Scripts/jquery-1.10.2.min.js"></script>
        <script src="Scripts/jquery.signalR-2.0.0.min.js"></script>
        <script src="Scripts/angular.min.js"></script>
        <script src="Scripts/bootstrap.min.js"></script>
        <script src="Scripts/chart.min.js"></script>
        <script src="Scripts/angles.js"></script>
        <script src="Scripts/app/app.js"></script>
        <script src="/signalr/hubs"></script>
    </div>


I sure learned a lot from this very small SPA and I hope that some of you have too.

Cheers,

Hugo

My very own Taskmanager in HTML 5, SignalR and Nancy

image

Last week I had the crazy idea to create my own Single Page Application (SPA) HTML page that could show the Total CPU on my machine much like the traditional Taskmanger in Windows. I went through a couple of different blogs to get started with like this excellent one Tutorial: Server Broadcast with SignalR 2.0.

I knew I wanted to use:

  • HTML 5
  • some simple and nice looking chart JavaScript library
  • SignalR, to push notifications to my client without refreshing the webpage
  • Nancy, the simplest way to create an API that I could ask for current CPU utilization
  • AngularJS, seems like a very cool JavaScript library that I wanted to learn more of
  • Visual Studio 2013

The end result I was aiming for was something like in the picture below:

image

The Web Application

First of all I created a new ASP.NET Web Application project.

image

I choose the Empty template because I didn’t need any unnecessary files clogging my solution.

image

Next thing I did was to set the Web Application port to 8080 so that I know how to call my Nancy api later on.

image

 

In with SignalR

To get started with SignalR in Visual Studio 2013 you just add a SignalR Hub Class to your project.

image

This will immediately pull down and install A LOT of NuGet packages as you can see below…

image

image

and some helpful JavaScript files are downloaded and neatly placed in the Scripts folder, thanks! There’s also a hub class created that we’ll look at in a while.

image

Next I added a OWIN Startup class.

image

And then I added the usual app.MapSignalR(); statement and went on to the next task.

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(HugoHaggmark.Taskmanager.Startup))]

namespace HugoHaggmark.Taskmanager
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

The CPU API with Nancy

The fantastic people behind Nancy had already made sure there was an OWIN package available for me so using Install-Package Nancy.Owin I pulled down some more awesome code.

image

Now I’m using my Nancy module in the same Web Application, but that’s not necessary you could call an existing Nancy module somewhere else but in the purpose of keeping it simple and clean I use the same Web Application here. Add the UseNancy() statement in the startup class to host Nancy in OWIN and let’s move on.


namespace HugoHaggmark.Taskmanager
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app
                .MapSignalR()
                .UseNancy();
        }
    }
}

Next step is to add the actual Nancy module which is a plain class that inherits from NancyModule.

image

using Nancy;
using System;
using System.Diagnostics;
using System.Threading;
namespace HugoHaggmark.Taskmanager.Modules
{
    public class CpuModule : NancyModule
    {
        PerformanceCounter cpuCounter;

        public CpuModule()
            : base("api/cpu")
        {
            InitializePerformanceCounter();
            Get["/"] = x =>
            {
                int cpu = (int)Math.Ceiling(cpuCounter.NextValue());

                return Response.AsText(cpu.ToString());
            };
        }

        private void InitializePerformanceCounter()
        {
            cpuCounter = new PerformanceCounter();

            cpuCounter.CategoryName = "Processor";
            cpuCounter.CounterName = "% Processor Time";
            cpuCounter.InstanceName = "_Total";
            cpuCounter.NextValue();
            Thread.Sleep(1000);
        }
    }
}

Let’s go through this piece of code. Firstly the constructor tells Nancy that it will respond to requests to “api/cpu”. Next thing is to initialize a performance counter for Total CPU and then sleep for a second which according to a lot of blog posts out there is crucial or the NextValue() method will return 0;

So now we have a very lightweight and simple api that will return the total CPU on a machine.

The Broadcasting class

The next thing I wanted to create was a class that broadcasts to all connected clients the current total CPU. So from the the Tutorial post mentioned earlier I found a way to do this. Add a simple class.

image

As I’m going to call the api in Nancy I want to use the HttpClient that is found in the System.Net.Http assemblies so I added those.

image

Then I just added the example from the Tutorial mentioned above and refactored the code to suite my needs.

using HugoHaggmark.Taskmanager.Hubs;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Net.Http;
using System.Threading;

namespace HugoHaggmark.Taskmanager
{
    public class Broadcaster
    {
        private readonly static Lazy<Broadcaster> instance = new Lazy<Broadcaster>(() =>
            new Broadcaster(GlobalHost.ConnectionManager.GetHubContext<CpuHub>().Clients)
            );

        private readonly TimeSpan updateInterval = TimeSpan.FromMilliseconds(500);
        private readonly Timer timer;

        private Broadcaster(IHubConnectionContext clients)
        {
            Clients = clients;

            timer = new Timer(BroadcastCpuUsage, null, updateInterval, updateInterval);
        }

        public static Broadcaster Instance
        {
            get
            {
                return instance.Value;
            }
        }

        private IHubConnectionContext Clients
        {
            get;
            set;
        }

        private void BroadcastCpuUsage(object state)
        {
            string cpu = GetCurrentCpu();

            Clients.All.cpuInfo(Environment.MachineName, cpu.ToString());
        }

        private string GetCurrentCpu()
        {
            string currentCpu = "0";

            HttpClient client = new HttpClient();
            client.BaseAddress = new Uri("http://localhost:8080");

            var response = client.GetAsync("api/cpu").Result;
            if (response.IsSuccessStatusCode)
            {
                currentCpu = response.Content.ReadAsStringAsync().Result.ToString();
            }

            return currentCpu;
        }
    }
}

Code specific to my Broadcaster implementation is the GetCurrentCpu method that calls our Nancy api and broadcasts the result to all connected clients. All other code is ripped from the SignalR tutorial mentioned earlier.

Returning to the Hub

Our Hub will be really simple to implement at this point.

using Microsoft.AspNet.SignalR;

namespace HugoHaggmark.Taskmanager.Hubs
{
    public class CpuHub : Hub
    {
        private readonly Broadcaster broadCaster;

        public CpuHub()
            : this(Broadcaster.Instance)
        {

        }

        public CpuHub(Broadcaster broadCaster)
        {
            this.broadCaster = broadCaster;
        }
    }
}

The only thing we need to do is to instantiate the Broadcaster Singleton and of we go!

Putting it all together in the HTML

Create a simple HTML Page.

image

I found this awesome JavaScript library for Charts called ChartJS that adds awesome chart capabilities to your HTML. So start of with downloading and adding every JavaScript file to the body like below.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Taskmanager</title>
</head>
<body>
    <canvas id="canvas" height="450" width="600"></canvas>

    <script src="Scripts/jquery-1.10.2.min.js"></script>
    <script src="Scripts/jquery.signalR-2.0.0.min.js"></script>
    <script src="Scripts/chart.min.js"></script>
    <script src="/signalr/hubs"></script>

And for the chart we need a canvas so I went ahead and created one as well.

Next off is to add the mandatory SignalR hub stuff in a inline JavaScript.

<script type="text/javascript">
    var cpuHub = $.connection.cpuHub;
    
    cpuHub.client.cpuInfo = function (machineName, cpu) {
        <!-- Insert stuff here to do when server broadcasts -->
    } 
    
    $.connection.hub.start();
</script>

There is 2 very important things to note with the JavaScript above:

  1. Firstly the $.connection.cpuHub statement must match the name of the hub class you’re connecting to
  2. In the Broadcaster class we’re calling  Clients.All.cpuInfo(Environment.MachineName, cpu.ToString()); so we need to register the same method in our client like the cpuHub.client.cpuInfo statement above does.

The rest is boilerplate code to make the chart work and look nicely and you can find all the code here.

I didn’t have to time yet to finish of the Angular part of this but I’m sure I’ll write another post soon about it.

Hope you enjoyed it,

Hugo