Sync clients with SignalR

SignalR gives you a very easy framework to sync clients. In the ComicsTale project we used it to show a client that another client has changed the same story so he can refresh and see the changes. Before using SignalR, you need to download the nuget package Microsoft ASP.NET SignalR. SignalR is called from client side. You need to create a client side proxy that will connect to server side SignalR component. Full project can be found on GitHub. Create a class that inherits Microsoft.AspNet.SignalR.Hub:

[HubName("comicStoryNotificationsHub")]
public class StoryNotificationsHub : Hub
{
    // called by the client when editing a story
    public void Join(string groupId /*actualy this is the story Id*/)
    {
        Groups.Add(Context.ConnectionId, groupId);
    }
    
    public void NotifyHasUpdates(string groupId)
    {
        Clients.OthersInGroup(groupId).notifyHasUpdates();
    }
}

This will create a proxy in javascript, see that join and notifyHasUpdates are exposed in client side:

/*!
 * ASP.NET SignalR JavaScript Library v1.1.1
 * http://signalr.net/
 *
 * Copyright Microsoft Open Technologies, Inc. All rights reserved.
 * Licensed under the Apache 2.0
 * https://github.com/SignalR/SignalR/blob/master/LICENSE.md
 *
 */

/// <reference path="..\..\SignalR.Client.JS\Scripts\jquery-1.6.4.js" />
/// <reference path="jquery.signalR.js" />
(function ($, window) {
    /// <param name="$" type="jQuery" />
    "use strict";

    if (typeof ($.signalR) !== "function") {
        throw new Error("SignalR: SignalR is not loaded. Please ensure jquery.signalR-x.js is referenced before ~/signalr/hubs.");
    }

    var signalR = $.signalR;

    function makeProxyCallback(hub, callback) {
        return function () {
            // Call the client hub method
            callback.apply(hub, $.makeArray(arguments));
        };
    }

    function registerHubProxies(instance, shouldSubscribe) {
        var key, hub, memberKey, memberValue, subscriptionMethod;

        for (key in instance) {
            if (instance.hasOwnProperty(key)) {
                hub = instance[key];

                if (!(hub.hubName)) {
                    // Not a client hub
                    continue;
                }

                if (shouldSubscribe) {
                    // We want to subscribe to the hub events
                    subscriptionMethod = hub.on;
                }
                else {
                    // We want to unsubscribe from the hub events
                    subscriptionMethod = hub.off;
                }

                // Loop through all members on the hub and find client hub functions to subscribe/unsubscribe
                for (memberKey in hub.client) {
                    if (hub.client.hasOwnProperty(memberKey)) {
                        memberValue = hub.client[memberKey];

                        if (!$.isFunction(memberValue)) {
                            // Not a client hub function
                            continue;
                        }

                        subscriptionMethod.call(hub, memberKey, makeProxyCallback(hub, memberValue));
                    }
                }
            }
        }
    }

    $.hubConnection.prototype.createHubProxies = function () {
        var proxies = {};
        this.starting(function () {
            // Register the hub proxies as subscribed
            // (instance, shouldSubscribe)
            registerHubProxies(proxies, true);

            this._registerSubscribedHubs();
        }).disconnected(function () {
            // Unsubscribe all hub proxies when we "disconnect".  This is to ensure that we do not re-add functional call backs.
            // (instance, shouldSubscribe)
            registerHubProxies(proxies, false);
        });

        proxies.comicStoryNotificationsHub = this.createHubProxy('comicStoryNotificationsHub'); 
        proxies.comicStoryNotificationsHub.client = { };
        proxies.comicStoryNotificationsHub.server = {
            join: function (groupId) {
                return proxies.comicStoryNotificationsHub.invoke.apply(proxies.comicStoryNotificationsHub, $.merge(["Join"], $.makeArray(arguments)));
             },

            notifyHasUpdates: function (groupId) {
                return proxies.comicStoryNotificationsHub.invoke.apply(proxies.comicStoryNotificationsHub, $.merge(["NotifyHasUpdates"], $.makeArray(arguments)));
             },
        };

        return proxies;
    };

    signalR.hub = $.hubConnection("/signalr", { useDefaultPath: false });
    $.extend(signalR, signalR.hub.createHubProxies());

}(window.jQuery, window));

When a client starts, he need to decide what to do when he gets a message from server and to call the join method:

private initConnection() {

    // Proxy created on the fly 
    var storyNotifications = $.connection.comicStoryNotificationsHub;

    // Declare a function on the chat hub so the server can invoke it          
    storyNotifications.client.notifyHasUpdates = () => {
        console.log('Receive updates notification!');
        this.hasUpdates(true);
    };

    // Start the connection
    $.connection.hub.start()
        .done(() => {
            console.log('Now connected, connection ID=' + $.connection.hub.id + ', transport=' + $.connection.hub.transport.name);
            storyNotifications.server.join(this.storyId);
        })
        .fail(() => { console.log('Could not Connect!'); });
}

The connection name comicStoryNotificationsHub is determined in the HubName attribute on the top of our StoryNotificationsHub class. We create a new method notifyHasUpdates that when called will show a refresh button by notifying the observable collection of knockout. Then we start the connection and when it is started we call the join method to join the group of clients dealing with the same comic story. When a client change something in a story, all he needs to do is call the server NotifyHasUpdates method through the proxy:

private notifyUpdated()
{
    $.connection.comicStoryNotificationsHub.server.notifyHasUpdates(this.storyId);
}

The NotifyHasUpdates method on the server side will call the notifyHasUpdates (that initiated in initConnection client method) on each client except from the caller client.

Thank you for your interest!

We will contact you as soon as possible.

Send us a message

Oops, something went wrong
Please try again or contact us by email at info@tikalk.com