Skip to content

SignalR Connection Indicator reloaded

Alt
Some time ago I’ve written a blog post on how to build a connection indicator using ASP.Net MVC, SignalR and TypeScript. I had to write one again and did an improved version of the previous one by better using Knockout.js.

The final result looks like the initial graphic. So the connection indicator will change its color and text to reflect the online state of the app.

It also uses a view-model that is bound using databinding to the DOM elements. So we are talking of a MVVM pattern here. As the basic principal still applies we can go straight to the code.

SignalR Hub

First of all I implemented a very simple SignalR Hub in C# using Visual Studio 2015 “Add new item” dialog:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;

namespace WebApplication1.Hubs
{
    public class ConnectionStateHub : Hub
    {
        public void Hello()
        {
            Clients.All.hello();
        }
    }
}

Razor Layout

This time I’ve integrated the connection indicator in the Razor layout _Layout.cshtml so its rendered on all my pages. The layout is a standard Bootstrap 3 page with Knockout.js loaded. The important part of code is this one:

<div class="navbar-header pull-left connectionStateNav">
    <span id="connectionStateContainer" style="font-size:18px;" class="hidden">
        <span class="label label-default">
            <span></span> 
        </span>
    </span>
</div>

Its basically a Bootstrap label with a span inside it. I use just these two elements with databinding for Knockout.js. The outer label binds its CSS class to connectionStateCssClass while the inner Span binds its text to text to connectionStateText.

At the bottom of the _Layout.cshtml I have my TypeScript (Javascript) linked:

<script src="@Url.Content("~/Scripts/MyAppOnlineState.js")"></script>
<script href="http://~/signalr/hubs">http://~/signalr/hubs</script>

My external script file gets initialized using the following Javascript code at the bottom of the _Layout.cshtml:

$(function () {
    window.connectionState = new MyApp.Core.OnlineStateViewModel();
    ko.applyBindings(window.connectionState, $(".connectionStateNav").get(0));
    window.connectionState.startSignalRHub($.connection);
    $("#connectionStateContainer").removeClass("hidden");
});

It loads my TypeScript code from MyAppOnlineState.js and the SignalR hub from the virtual reference signalr/hubs. Then it create a new instance of MyApp.Core.OnlineStateViewModel and apply the Knockout databinding using the data- attributes for all elements within my connection state DIV. Finally the SignalR Hub gets started and the connection indicator shown by removing the hidden class.

Viewmodel OnlineStateViewModel

Now you probably as for the MyAppOnlineState.js which is a TypeScript file so its source-code is in *MyAppOnlineState.ts. Here we go:

namespace MyApp.Core {

    export class OnlineStateViewModel {
        private connectionStateHub: any;
        private subscriptions: Array&lt;Function> = [];

        public connectionState = ko.observable(4);
        public connectionStateText = ko.observable("");
        public connectionStateCssClass = ko.observable("info");

        public subscribeStateChange(callback: Function): void {
            this.subscriptions = this.subscriptions.concat(callback);
        }

        public startSignalRHub(connection: any) {

            this.connectionStateHub = connection.connectionStateHub;
            this.connectionStateHub.client.hello = () => {
                console.info("SignalrR: hello() received.");
            }

            connection.hub.stateChanged((state: any) => this.connectionStateChanged(state));
            connection.hub.disconnected(() => {
                setTimeout(() => { connection.hub.start(); }, 10000); // Restart connection after 5 seconds.
            });
            connection.hub.start();
        }

        public connectionStateChanged(state: any): void {
            const stateConversion = { 0: "Verbinde", 1: "Online", 2: "Neu verbinden", 4: "Offline" };
            const stateClass = { 0: "label-default", 1: "label-success", 2: "label-warning", 4: "label-danger" };

            console.log("SignalR state changed from: " + stateConversion[state.oldState] + " to: " + stateConversion[state.newState]);

            this.connectionState(state.newState);
            this.connectionStateText(stateConversion[state.newState]);
            this.connectionStateCssClass("label " + stateClass[state.newState]);

            this.subscriptions.forEach(f => f(state));
        }
    }
}

The viewmodel basically sets things up in startSignalRHub. Important here is the assignment of the eventhandler stateChanged calls the connectionStateChanged() function. This one calculates new values for the properties connectionState, connectionStateText and connectionStateCssClass.

The viewmodel also features a subscriber mechanism where other scripts can add them so they get notified if a connection state change happens. They can do so by calling the function subscribeStateChange() with the callback to call in case of the connection state changes.

marcd View All

I love to write software. More then two decades ago I managed to make my hobby my full-time job so I spent more then 20 year writing professional software (I guess that makes me a "Senior Software Developer"). The last few years I spend most of the time developing in C#/.Net for all kind of windows-, web- and embedded-software.

In my free time I enjoy my family, taking photos and go diving in cold lakes and rivers in Switzerland.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: