SignalR Coding Best Practices – Revisited


Introduction

Nearly 3 years ago, I wrote a short blog post on basic best practices for using SignalR to add real-time functionality to web applications. Since then, our teams at Sinara have built many more web applications of varying complexity using SignalR, and have gradually refined our technique over time. In this blog post, I’ll be sharing a few tips we have discovered along the way while applying this technology in the real world:

  • Avoid OnReconnected
  • Use strongly-typed hubs
  • Be careful what you send
  • Use TypeScript with SignalR
  • Generate TypeScript declarations for your SignalR calls

I would recommend all of the above to anyone who wants to improve the way they use SignalR in projects large and small.

Avoid OnReconnected

Don’t worry about overriding the hub’s OnReconnected method – it’s not as useful as it might appear

A big part of the problem here is a common misunderstanding of the lifespan of a SignalR connection (known internally as a persistent connection). The official documentation has full details on this topic, but in summary, a Reconnect is a network level reconnection where the persistent connection continues uninterrupted as normal. In other words, it’s not actually an application-level disconnection followed by a reconnection during which messages might be lost. To avoid confusion, the method would ideally be renamed to something like ‘OnRecoveredAfterNetworkBlip’!

If I were to write the description for it myself, I would say:

… a client has reconnected their network connection within a short time frame; they have been able to recover their SignalR PersistentConnection and thus their messages from SignalR seamlessly

Note that I’ve suggested “seamlessly”, meaning there’s no need for your app to worry about it, to republish any page state or do anything except possibly log that the user has had a minor network issue. This seems to happen quite a lot across the Internet, so don’t get your application support teams worried – everything is fine!

There are two related comments that I would add:

  • OnReconnected may also be called if the user fails over from a different web node. Multi-node SignalR setups is a complex topic in itself – to be covered in a future blog post!
  • The JavaScript client’s ConnectionSlow callback is triggered if the network connection drops, but the connection may still be recovered. This is another one that is safe to ignore – if the connection recovers, then everything is fine. If it drops altogether, the Disconnected callback will be triggered, and is a much better place to handle disconnection logic.

Use strongly-typed hubs

The most obvious benefit of a strongly-typed hub is ensuring you don’t accidentally misspell a client call or pass the wrong arguments. This should be a good enough reason by itself. However, by using strongly-typed hubs, you get other benefits which are explored below:

  1. It allows you to generate TypeScript declarations for your SignalR hubs and callbacks
  2. This might not apply to everyone, but it has allowed us to write tools to analyse the data sent between the server and the client

Use strongly-typed hubs – it’s worth it

Be careful what you send

This is an interesting one, and can be pretty hard to spot. At Sinara, we’ve actually written a tool using .NET Reflection to help with the code validation process in this area.

Do you use an ORM with SignalR? Quite likely you do, as it’s the suggested approach from Microsoft. At Sinara we generally use Entity Framework, either Code First or Database First. If you decide to send an object from your ORM through your SignalR hub to a JavaScript client, it is important you know exactly what data is being sent across the wire. Let’s think up some examples:

  • If you’re writing an app that allows users to monitor their loan request status, you don’t want to include any private notes about why the loan was rejected to be serialised into JSON for a user to potentially see!

Remember, just because you’re not displaying the details (such as these private notes), it isn’t hard for someone to use browser developer tools to view these (slightly) hidden details. What about a more dangerous one:

  • What about the name of the bank administrator who rejected the case?
  • What about their password hash…!

To make sure scenarios like this never happen, there’s one obvious solution:

Avoid putting Entity Framework (or any ORM) objects directly through your hub – use a DTO instead

This can be an especially awkward change to make if you have an existing system, especially if it’s for a page that’s already working well. So a few rules for a compromise:

  1. Never allow serialisation of data items that aren’t intended for display on the front end (e.g. password hash, password history, or one time passwords etc.) – just put a [JsonIgnore] attribute (or similar) around it. There’s no reason for it to be available on the front end.
  2. Sanitise the data sent/returned to each page and test that your sanitation works!

One thing we at Sinara have done is develop a small tool that makes use of reflection and strongly-typed hubs, parses the assembly and prints out exactly what is sent through each call. This allows us to validate that only the relevant data is sent through.

Use TypeScript with SignalR

I hope this one is more obvious. TypeScript has taken over the world, and JavaScript is out – I plan on never writing a system with vanilla JavaScript again! If you haven’t yet taken a look at TypeScript, I highly recommend trying it out. It’s easy to switch to TypeScript from JavaScript, and if you’re still cautious, you can even do it slowly as most JavaScript is also valid TypeScript. Even Angular.Js is written in TypeScript.

So why would you still use JavaScript? Well, there are a couple of reasons, not least because all SignalR examples online seem to use JavaScript, and the SignalR interfaces aren’t available in TypeScript out of the box (see below for help with that!). But even if you have to write out the TypeScript declarations manually, it will help with all sort of coding issues.

But wait, why don’t we just automatically generate the TypeScript declarations? Well, we do exactly that, which brings us to the next point.

Generate TypeScript declarations for your SignalR calls

If you’re using TypeScript, this part is a huge help and I wouldn’t do a project without it anymore. When you write your hub code in C#, wouldn’t it be ideal if you could get TypeScript declarations automatically generated for you?

At Sinara, we have tackled this problem by creating a dedicated set of .NET assemblies that are able to parse out the hubs, the hubs’ client interfaces (assuming you’re using strongly-typed hubs again), and any types that may be used within those, and write out equivalent TypeScript declarations. We then make use of a T4 template to allow the TypeScript to be generated within a Visual Studio project context.

The process started small, only handling the hubs themselves. As we saw the benefit, we gradually improved it to include special handling of lists, dictionaries, enums and nullables.

By using this approach, all invocations of SignalR throughout the project are type-safe and checked ahead of time using Intellisense, meaning better quality code and fewer issues going forward.

Always use TypeScript and always script out TypeScript declarations for your SignalR hubs

SignalR is a huge topic and there are more areas we may cover in future blog posts:

  • Using SignalR’s hub pipeline
  • What architecture works best?
  • Scaleout and backup sites
  • The Future: SignalR 3