SignalR – the right way

It has been quite sometime that SignalR has been around, 2014, so why are not more people using it? First what is SignalR?

ASP.NET SignalR is a library for ASP.NET developers that makes developing real-time web functionality easy. SignalR allows bi-directional communication between server and client. Servers can now push content to connected clients instantly as it becomes available. SignalR supports Web Sockets and falls back to other compatible techniques for older browsers. SignalR includes APIs for connection management (for instance, connect and disconnect events), grouping connections, and authorisation.

For further information check https://www.asp.net/signalr

Could it because people just don’t get it or really understand what it can do, or perhaps they hit issues without realising they don’t understand the structure?  Whatever the reason people are not using SignalR, I’m going to provide a useful sample application to get you off on the right footing.

There are a lot of different samples and information on how to use SignalR, but I’m always a firm believer of KISS (Keep It Simple Stupid).

There are two primary sides to SignalR, the client side and the server hubs, here I have created an MVC application with Individual User Accounts for Authentication.

First, add the SignalR NuGet package

Install-Package Microsoft.AspNet.SignalR

Then we need to map the Hubs connection to the application.

To enable SignalR in your application, create a class called Startup with the following:

using Microsoft.Owin;
using Owin;
using MyWebApplication;

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

What is important here is that app.MapSignalR() is the last to be called, and this is because any changes to the app need to be done before you call the mapping.  The incorrect order got me once when we had some custom Authentication, and it was not being passed to SignalR hubs.

I won’t be going into how you go about setting up the step by step process, as this is documented in many places, and also comes in the readme.txt file as part of the NuGet package.

What I will be adding is the Authorization to the project, which is covered by Microsoft in Authentication and Authorization for SignalR Hubs.

What is important to note how the connection is handled, we are using a class called SignalRConnectionManager, and this controls the connections based on the username coming from the context and the connection id which also comes from the context.

 

public class SignalRConnectionManager<T> : IDisposable
 {
 private readonly ConcurrentDictionary<T, HashSet<string>> _connections = new ConcurrentDictionary<T, HashSet<string>>();

public int Count { get { return _connections.Count; } }

/// <summary>
 /// Attempts to add the specified userid and connectionid
 /// </summary>
 public void Add(T userid, string connectionid)
 {
 HashSet<string> connections = _connections.GetOrAdd(userid, new HashSet<string>());

lock (connections)
 {
 connections.Add(connectionid);
 }
 }

public IEnumerable<string> Connections(T userid)
 {
 HashSet<string> connections;
 if (_connections.TryGetValue(userid, out connections))
 {
 return connections;
 }

return Enumerable.Empty<string>();
 }

public IEnumerable<T> UserIds()
 {
 return _connections.Keys;
 }

/// <summary>
 /// Attempts to remove a connectionid that has the specified userid
 /// </summary>
 public void Remove(T userid, string connectionid)
 {
 HashSet<string> connections;
 if (!_connections.TryGetValue(userid, out connections))
 {
 return;
 }

lock (connections)
 {
 connections.Remove(connectionid);

if (connections.Count == 0)
 {
 HashSet<string> emptyConnections;
 _connections.TryRemove(userid, out emptyConnections);
 }
 }
 }

#region IDisposable Support

private bool disposedValue = false; // To detect redundant calls

protected virtual void Dispose(bool disposing)
 {
 if (!disposedValue)
 {
 if (disposing)
 {
 _connections.Clear();
 }

// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
 // TODO: set large fields to null.

disposedValue = true;
 }
 }

// This code added to correctly implement the disposable pattern.
 public void Dispose()
 {
 // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
 Dispose(true);
 // TODO: uncomment the following line if the finalizer is overridden above.
 // GC.SuppressFinalize(this);
 }

#endregion IDisposable Support
 }

Client Code

In my case I’m going to be looking at JavaScript within a C# MVC application, which looks like this:

<p>SignalR</p>
<!--The jQuery library is required. -->
<script src="~/Scripts/jquery-1.10.2.js"></script>
<!--Reference the SignalR library. -->
<script src="~/Scripts/jquery.signalR-2.2.3.min.js"></script>
<!--Reference the auto generated SignalR hub script. -->
<script src="~/signalr/hubs"></script>

<!--Add script to update the page and send messages - SignalR - HeartBeat.-->
<script type="text/javascript">
 $(function () {
 // Declare a proxy to reference the hub.
 var heartBeat = $.connection.heartBeatHub;

heartBeat.client.broadcastMessage = function (html) {
 $('#message').html(html).fadeIn();
 };

if ($.connection.hub && $.connection.hub.state === $.signalR.connectionState.disconnected) {
 $.connection.hub.start()
 .done(function () {
 console.log('SignalR now connected, connection ID=' + $.connection.hub.id);
 heartBeat.server.send('Heart beat listening');
 console.log("Heart beat started")
 })
 .fail(function () { console.log('Could not Connect!'); });
 }
 });
</script>
<div id="message">
</div>

two important lines in this code are:

Reference the auto generated SignalR hub script

<script src="~/signalr/hubs"></script>

Declaring the proxy to reference the hub, you’ll notice the case of the letter ‘h’ is different the the C# code, this is important otherwise you will get a JavaScript error in your browser.

var heartBeat = $.connection.heartBeatHub;

Another important thing to note is that you should only start the hub once, no matter how many SignalR endpoints you have, and you place the listening code within the done section of hub, I’ve commented out another listening hub in this sample code:

if ($.connection.hub && $.connection.hub.state === $.signalR.connectionState.disconnected) {
 $.connection.hub.start()
 .done(function () {
 console.log('SignalR now connected, connection ID=' + $.connection.hub.id);
 heartBeat.server.send('Heart beat listening');
 console.log("Heart beat started")
 //anotherHub.server.send('Another hub listening');
 })
 .fail(function () { console.log('Could not Connect!'); });
 }

That is it for now, a good clean SignalR project, and here it is: SignalR