SignalR Coding Best Practices – Revisited
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.
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!
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:
- It allows you to generate TypeScript declarations for your SignalR hubs and callbacks
- 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.
- 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:
- 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.
- 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
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
- May 2021
- April 2021
- January 2021
- December 2020
- October 2020
- September 2020
- August 2020
- July 2020
- June 2020
- April 2020
- March 2020
- February 2020
- November 2019
- October 2019
- September 2019
- August 2019
- July 2019
- June 2019
- May 2019
- April 2019
- July 2018
- May 2018
- April 2018
- February 2018
- January 2018
- October 2017
- May 2017
- February 2016
- January 2016
- July 2015
- June 2015
- October 2014
- September 2014
- August 2014
- June 2014
- May 2014