SignalR is an easy to use framework for .NET and the browser (introduction) which enables bi-directional communication over a variety of transports (from WebSockets to Ajax long-polling). In particular the Hubs API allows both client-to-server and server-to-client calls to be made simply as method calls on a ‘hub’ proxy object. On the .NET side this makes use of dynamic method calls, while JavaScript is dynamic by nature—so although the communication is easy, it does not benefit from the safety guarantees and tooling support brought by strong typing. In this post I will summarise some existing tools which can be used to provide strong typing for the SignalR Hubs API between C# and TypeScript, as well as a more experimental approach via F#, FunScript and Type Providers.
As a running example I’m going to use the “chat” example from the SignalR tutorial (it’s worth reading through the tutorial if you struggle to follow this post). This provides a server hub with one method Send
which broadcasts a chat message to all connected clients, and a client hub with one method addNewMessageToPage
which displays the received message.
On the server side we have this hub which calls back to clients directly:
On the client side we have this:
TypeScript by hand
It’s surprisingly easy to convert the chat example to use TypeScript. From NuGet you can install typing definitions from DefinitelyTyped for jQuery and SignalR. Then in order to be able to compile, we need to add a definition for the client-side proxy for the server chat hub. At the simplest (and giving us nothing in the way of useful typechecking!) is the following:
We can of course do better by specifying the actual hub interfaces. An introduction to this approach can be found here.
Applying this interface definition to the top of the file, the existing jQuery and SignalR code compiles and runs as-is (but if you change the name of the addNewMessageToPage
or send
calls, you can see that it is flagged up as an error).
SRTS
The above process has helped make the types of the client/server hubs more explicit on the client side, but doesn’t help us keep in sync with the server: everything has to be written out twice. To fix this we can look to generate the TypeScript interfaces automatically from the server hub definition.
There are a couple of options available for this. Firstly there is the NuGet package SignalR.TypeScript
, based on the SRTS
generator tool. This wraps an executable which does the actual work of generating definitions based on a DLL, with a T4 template to initiate the generation process.
From our existing hub implementation, the tool generates the following typescript definition file:
In our implementation .ts
file we add the following (as well as a tweak to take care of changing versions of typescript definitions):
Then for each time we change the server hub, we simply regenerate the T4 template (and this can be arranged to happen on build). This gives us a guarantee that calls from the client to the server hub have the correct name and type, but no guarantee of any sort regarding calls in the other direction.
You can see a working example project of this approach here.
Hubs.tt
Another implementation of a similar idea is Hubs.tt
. This is implemented entirely as a T4 template (making use of SignalR libraries), and provides some additional functionality on top of the above version. However due to the nature of the template, some tweaking has to be done to reference the correct versions of various DLLs, or your hubs will not be picked up. The template is available as a Gist, and unfortunately this means that there are multiple forks to fix individual issues with no common best version containing all changes.
After dropping the template in the typings
directory of your project (where TypeScript typings from NuGet are typically deployed), the template will generate output similar to the above. However the “killer feature” is that we can add a C# definition of the client hub interface like this:
When we have an interface with the magic name I(HUBNAME)Client
then a corresponding TypeScript interface will be generated for the client hub.
Altogether this gives us the guarantee that calls from the client to the server hub have the correct name and type, and that the C# server code and the TypeScript client code share a corresponding type for the client hub methods (but no static checking of actual calls from server to client hub).
You can see a working example project of this approach here.
Calling client hubs
Edit: See update below, this is supported directly in SignalR 2.1
I said that the translation of a C# interface for a client hub to the corresponding TypeScript type was a killer feature, but it is not yet giving us
any form of safety, or much convenience other than having our definitions in one place. This is because the client hubs are accessed via the use of
dynamic
method invocations:
The hub has a IHubConnectionContext<dynamic>
which exposes various things such as Clients.All
, Clients.Caller
, or the ability to talk to clients
for named connection IDs, groups, etc.
One approach suggested to ensure strong typing makes use of conditional compilation to ensure that
the client interface (as we may have generated with Hubs.tt
) typechecks, while the actual compilation happens using the dynamic
object (as it has to
for SignalR to work). This is a bit hacky and frankly unsatisfying, so fortunately with the use of a Castle dynamic proxy, one can do much better. With this approach, we end up with a strongly typed wrapper to the client hub (using the interface
type which corresponds to the generated TypeScript interface), which delegates to the dynamic
hub proxy under the hood:
You can find a complete example here which puts everything together with Hubs.tt
plus this Castle proxy approach. Here every call
from client to server or server to client is strongly typed, and nothing is declared in two places which might go out of sync. The server hub class
is implemented, and a corresponding TypeScript interface will be generated for client to server calls; then an interface is defined in C# for the
client hub, which will be used on the server side (via Castle proxy) and will generate a TypeScript interface which must be implemented on the client side.
There’s no reason this approach can’t be used via the SRTS tool also: currently the client interface has to be defined by hand, but this could also be extended along the lines of Hubs.tt
(example updated in repository).
Calling client hubs - New and Improved
Since SignalR 2.1, strongly typed client hubs are supported out of the box (thanks Damian for pointing this out in comments). Instead of Hub
you can just use the Hub<T>
type, where T
is the interface type of your client hub. So the above looks like this:
Much better. This looks just the same as the dynamic
version, but Clients.All
genuinely does have the
client hub type IChatHubClient
. Sadly the SRTS tool doesn’t currently recognise the Hub<T>
type, but there would be no obstacle to doing so; this works well together with Hubs.tt
as above.
F# Type Provider
The approach defined above would be my recommendation. However I have been experimenting with a different kind of approach based on F# Type Providers. A work in progress is available on GitHub. F# Type Providers basically allow for code generation at compile/edit time to give code completion & type safety over some existing API. Common examples are web services, database access, or CSV/XML files. Together with this I’m using FunScript, an F#-to-JavaScript transpiler, for the client side. Then a type provider is used to access the client hub types from the server, and vice versa.
The server project defines a SignalR Hub class (and I’m using a simpler example for this section):
The client is written using FunScript, uses the existing FunScript bindings for SignalR, and has a reference to the server DLL. FunScript and the bindings for common libraries (those with bindings in the DefinitelyTyped project) are available via NuGet, while some simple wrapper code is needed to generate the corresponding JavaScript. The client references the SignalRProvider, which searches for hubs (by attribute) in the referenced DLLs, and exposes these as new types:
Then when a method on the hub is called, it will be typechecked at compile time of the FunScript code, and code will be emitted to call the corresponding SignalR JavaScript methods.
We also get intellisense on these methods, so that whenever the server hub API is updated, we can see the full list of methods when writing the client side.
The current version of this provider is unfinished, in particular lacking support for server calls to client hubs, so I can’t give the same chat example here.
Conclusion
I really like SignalR and the ease with which it enables bidirectional communication (and particularly server-client push notifications) abstracting over the underlying transport. The combination of TypeScript and a couple of the tools outlined above resolve the one thing that really bugged me about SignalR, the lack of type-checking, without proving too burdensome.
It has been fun (if frustrating) to build the SignalR type provider, and I’ve certainly learned a few obscure things, but I can’t quite recommend anybody should use it yet!