SignalR: Getting into a right state - connections and defensive coding

Hackered
Monday, March 9, 2015
by Sean McAlinden

SignalR handles all the complexities of persistent connections enabling us to build really cool rich client applications.

Clearly a crucial feature of this is SignalR handling the re-initialization of connections when a connection cannot be saved.

This is an area where bugs stealthy enter our applications which by their nature are hard to reproduce and diagnose.

Connection Identifiers and state

When using SignalR our main identifier for the client is the Connection Id, we can use this to understand where the message is coming from and where to send messages to.

As this is our main identifier for a user connection it seems to be the logical candidate for correlating useful state for the user session.

So where is the issue?

SignalR does a great job attempting to keep connections alive, however connections will be lost and if SignalR decides it cannot be saved it will re-establish a brand new connection, other than a potentially small delay this will happen transparently to the user.

However for us developers a brand new connection means a brand new connection id

This is where we can easily get into trouble when using the connection identifier to access user state information.

What can be done?

Well luckily this is not a difficult problem to solve, the key is knowing that it can happen and code accordingly.

I tend to treat my SignalR hubs the same way as any other public API endpoint and the SignalR connection identifiers as little transient identities for my user.

For example: 

On each call (including hub start and all subsequent calls) I identify the user via an authorization header or cookie and then attach the connection identity to my hub user.

If on a subsequent call I receive a call from the same user but with a different connection id I simply overwrite the existing connection id attached to my hub user.

Can I see this in action?

There are some useful methods you can use either in a hub or on the front end which can be used to trace/handle these scenarios.

On the .Net hub side there are 3 main method overrides which can be used to track the connections:

  • OnConnected
  • OnDisconnected
  • OnReconnected

OnConnected

This is called when a new connection is created and assigned to the calling client.

public override Task OnConnected()
{
	var user = GetUser(Context.User);
	var connectionId = Context.ConnectionId;
	Trace.WriteLine("User id {0} is using connection id {1}", userId, connectionId);
	return base.OnConnected();
}

OnReconnected

This is called when SignalR could save the same logical connection keeping the same connection identifier.

public override Task OnReconnected()
{
	var user = GetUser(Context.User);
	var connectionId = Context.ConnectionId;
	Trace.WriteLine("User id {0} is still using connection id {1}", userId, connectionId);
	return base.OnReonnected();
}

OnDisconnected

This is called when SignalR could not save the same logical connection thus creating a brand new connection with a new connection identifier.

public override Task OnDisconnected()
{
	var user = GetUser(Context.User);
	var connectionId = Context.ConnectionId;
	Trace.WriteLine("User id {0} is now using connection id {1}", userId, connectionId);
	return base.OnDisconnected();
}

Handling events at the client

To handle the events on the client you can use the hub client object like the following:

hub.starting(function(){
    alert('We do not have a connection id yet');
})
hub.reconnecting(function(){
    alert(hub.id);
})
hub.reconnected(function(){
    alert(hub.id);
})
hub.disconnected(function(){
    alert(hub.id);
})

I hope this has been helpful, the main thing to remember is that even though SignalR is awesome, it is still working on a big and often flaky network (the internet) and whilst it does it's best, it cannot perform actual magic so be prepared.