Intellisense on SignalR dynamics

In my previous post I mentioned a limitation when combining SignalR and TypeScript, how we couldn’t take advantage of the client interface definition, that we created for TypeScript’s use, to also get Intellisense when coding on the server’s C# side. I suggested that perhaps there was a way to do it with conditional compilation flags, and today I thought I’d spend some time figuring it out…

…which didn’t take very long at all. What I now have in each of my Hubs is code like the following:

#if Debug
public dynamic Others { get { return Clients.Others; } }
public dynamic All { get { return Clients.All; } }
public dynamic Self { get { return Clients.Caller ; } }
#else
public IChatHubClient Others { get { return Clients.Others as IChatHubClient; } }
public IChatHubClient All { get { return Clients.All as IChatHubClient; } }
public IChatHubClient Self { get { return Clients.Caller as IChatHubClient; } }
#endif

This now lets me use a line such as

All.addNewMessageToPage(msg);

in the server’s Send() function, and I get Intellisense providing addNewMessageToPage() as a suggestion, and I get a pre-compile-time check that the msg parameter is correct.

This works because at editing time, Debug is not defined, so I get the IChatHubClient casting of the different dynamic objects that I wish to use Intellisense on and have the pre-compiler check against. When compiled, however, Debug is defined, and the proper dynamic objects are used, which allows the SignalR library to happily send the requests to the client-side functions I’ve claimed are there.

Once I’m ready to move this to production, I’ll have to add an additional Release or something similar, to ensure the code still compiles correctly.

Oh, and for those reading this that have no interest in using TypeScript with SignalR (shame on you!), you can still use this trick of defining the client-side interface and casting it; you just won’t be using the interface to also generate TypeScript definition files.

Advertisements

SignalR and TypeScript

As usual, I like to stay on top of new technologies, to keep my computing skills honed and my employability up. While I’ve been picking up lots of new things lately (the community never stops producing amazing tools), I want to talk a bit about two specific technologies: SignalR and TypeScript. Both are Microsoft technologies, but don’t let that turn you off, if you have an aversion to the company; Microsoft is creating some impressive tools that are available to those who aren’t bound to that side of the industry.

SignalR

SignalR is a library that allows real-time bi-directional communication. It’s certainly not the first technology to provide this – we’ve had sockets for a long time – but it provides this communication in a Remote Procedure Call (RPC) style, allowing the developers on both ends to simply pass data to local functions, and the data is seamlessly provided to the other end with little work required setting up connections, polling for data or serializing complex information. There are plenty of resources at ASP.NET/SignalR, including lots of videos with some now-iconic examples of potential use for this technology.

Though I won’t get into details here, SignalR has a versatile set of methods it uses to allow this real-time always-connected environment, from the latest Web Sockets technology to the old-school polling from the client. The client-side libraries are supported on modern browser, most mobile browsers, and even some browsers considered not-so-modern; there are also libraries developed or being developed for other platforms beyond the browser, such as .NET, Android and iOS. Did I mention that SignalR is freely available on Github as well?

TypeScript

As soon as I heard about TypeScript, I was right on board. TypeScript provides a superset of JavaScript that helps to build large-scale applications for the browser, as well as the server side (with technology such as node.js). The main contribution TypeScript makes is providing type-checking at compilation time, allowing (but not requiring) you to specify data types for variables and parameters in your code. As JavaScript code gets longer, the likelihood of making mistakes in its dynamically-typed environment increase, leading to hard-to-find bugs and wasted time. TypeScript has a method for taking older JavaScript code and providing annotations to it, basically tacking on type information to your favorite libraries, from JQuery to AngularJS, to Backbone or your favorite homemade library. And the best part is that the TypeScript compiler generates pure, readable JavaScript, so there are no plugins or server-side modules required, nor is there anything tying you to TypeScript if you want to return to JavaScript-only ways. Like SignalR, TypeScript has some really good examples and videos that you can find at TypeScriptLang.org.

Example

I’m going to cheat a bit (quite a bit) by starting with a project created from one of the SignalR tutorials. There’s no point in me making up an example that’s nearly the same, nor in me going step-by-step through how to do it here — their screenshots are better than what I’d provide.

What we’re given is a simple chat system that multiple people can connect to and chat through. It provides a function on the server that the client calls, to post a message; and a function on the client that the server calls, to inform all parties when a message has been provided.

chatex

But let’s take a look at the JavaScript being used.

        $(function () {
            // Reference the auto-generated proxy for the hub.
            var chat = $.connection.chatHub;
            // Create a function that the hub can call back to display messages.
            chat.client.addNewMessageToPage = function (name, message) {
                // Add the message to the page.
                $('#discussion').append('<li><strong>' + htmlEncode(name)
                    + '</strong>: ' + htmlEncode(message) + '</li>');
            };
            // Get the user name and store it to prepend to messages.
            $('#displayname').val(prompt('Enter your name:', ''));
            // Set initial focus to message input box.
            $('#message').focus();
            // Start the connection.
            $.connection.hub.start().done(function () {
                $('#sendmessage').click(function () {
                    // Call the Send method on the hub.
                    chat.server.send($('#displayname').val(), $('#message').val());
                    // Clear text box and reset focus for next comment.
                    $('#message').val('').focus();
                });
            });
        });
        // This optional function html-encodes messages for display in the page.
        function htmlEncode(value) {
            var encodedValue = $('<div />').text(value).html();
            return encodedValue;
        }

There are two SignalR-related bits here: there’s the client-side function, addNewMessageToPage, that the server calls when it wants to inform all parties of new chats. The second is the call to chat.server.send(), which is how the client is sending a message to the server.

Note how there is no code setting up websockets, no code creating XHR or POSTs or the like. Just before this block of code are two other script includes,

    <!--Reference the SignalR library. -->
    <script src="~/Scripts/jquery.signalR-2.0.2.min.js"></script>
    <!--Reference the autogenerated SignalR hub script. -->
    <script src="~/signalr/hubs"></script>

which does all the setup for us, allowing us to write code that looks like local functions being called, on both sides. The C# portion in the Home controller is similarly formed, a local function that turns around and calls another local function.

Making it better

The problem with the JavaScript code above is that we have to know about the addNewMessageToPage function name that needs to be provided; with no Intellisense or other compile-time check, we could accidentally type AddMessageToPage and wonder for quite a while why things aren’t working. Likewise, we need to know that the server-side function is called send(), not sendMessage() or the like. And if you start creating a large application, with lots of functions being called in both directions, your likelihood of errors increases. Oh, and this is ignoring all of the JQuery mistakes we might make (not you, of course, but newer JQuery users like me). And we haven’t even touched the parameters being passed back and forth!

Adding TypeScript

Let’s take that JavaScript code in Chat.cshtml, and turn that into TypeScript. To get TypeScript support into your Visual Studio environment, you need the add-on from Microsoft. This can be downloaded from here.

Once TypeScript is available, we can start creating new TypeScript files. Since we already have a Scripts folder in our project, and that’s where other JavaScript is being included from, we’ll create the TypeScript file there — by default, JavaScript output is placed in the same location as the source TypeScript file; this is changeable from the command-line, and possibly from Visual Studio as well. Right-clicking on the Scripts folder, and selecting Add… I see TypeScript file as an option. We’ll select that and name it ChatHub, to match the naming used for the server-side code. When we create this script, Visual Studio asks us if we want to grab some typings from NuGet.

typings

This is a good time to go get some Definition files that will help out our existing code. Specifically, to help out the generated SignalR JavaScript, as well as the JQuery calls that we’re making in our chat client. In the Manage Nuget Packages dialog, we’ll search for these typing files that are part of the DefinitelyTyped project, a collection of community-written helper files that provide TypeScript with type data for existing JavaScript libraries that I mentioned earlier.

nuget2

By searching for “tag:typescript signalr”, we find the typings definition file for SignalR, which we can also see lists jquery’s definition file as a dependency, so we can grab both with one go.

Once we’ve installed those files, we’re given our blank ChatHub.ts file. We can get a good start by grabbing all of the JavaScript code above and pasting it in. We can do this, because JavaScript is valid TypeScript … mostly.

If we hadn’t installed those, two files from DefinitelyTyped, we’d end up with some like the following:

js1

js2

This is TypeScript’s way of telling us that it doesn’t know the definition of $, the JQuery library, and thus can’t confirm that any of those function calls are valid. With a single line at the top of our script

    var $:any;

we can shush TypeScript up, by saying that it’s a dynamic JavaScript object, so anything goes. But that doesn’t solve the problem of having the compiler (or our editor, with Intellisense) keeping us from making mistakes.

Because we did install the typing files, though, we should have only one complaint:

js3

This prevents the Intellisense (and the TypeScript) compiler from helping us out. We can quiet the error down with

    interface SignalR {
        chatHub: any;
    }

This is because the $.connection is being typed (with the SignalR typing file) as a “SignalR” type, and we can extend it easily with the above line. Unfortunately, while this hushes the TypeScript compiler, it isn’t any more useful than that. Instead, what we would have to do is define what that chatHub type is, which we can see from our usage in the code, means defining some type of “client” member, which as the “addNewMessageToPage()” method; and a “server” member, which has a “send()” method. Such as

    interface SignalR {
        chatHub: {
            client: {
                addNewMessageToPage(name: string, message: string)
            };
            server: {
                send(name: string, message: string)
            };
        };
    }

at the top of the file, or in our own ChatHub.d.ts file. If we now take that big block of JavaScript out of our Chat View, and replace it with

    <script src="~/Scripts/ChatHub.js"></script>

we get … exactly what we had. In fact, the resulting code is the same, because all we did was let TypeScript take our original JavaScript and pass it on through, unscathed. We got a little validation that our JQuery was well-formed, and that our SignalR matches what we claim in the interface definition above…

But this sounds like a maintenance nightmare. Every time I add a new function on either side of my SignalR connection, I have to remember to define it in this file, nearly duplicating my effort. If there was a way to automatically generate this code, then we could code once, and let software tools sort it out for us. Luckily, there are other folks out there that think like us, and have done the work for us!

Hubs.tt

hubs.tt is a T4 Template that will look through your code, find your SignalR Hub definitions, and create a TypeScript typing file from them. The original Hubs.tt was written by GitHub user robfe, but was improved upon and updated a bit by htuomola on GitHub. We take this and add it to our Scripts/typings folder, so its references to the SignalR and JQuery d.ts files are correct. (We could place it elsewhere, and modify the .tt file appropriately). If you’re new to T4 templates, as I was, then you should know that the template is run when first added to the project, and then only when manually launched afterwards — it is not part of the project build process to execute T4 templates automatically.

If everything went fine, what you should now have is an error:

Duplicate identifier ‘chatHub’.

This is because the definition generated by Hubs.tt is now conflicting with the handmade definition we applied above. If we now remove our temporary hush code, we should still get no errors, as Hubs.d.ts takes over.

The result of the Hubs.tt template can be found as a child element in the Solution Explorer. With comments removed, this is the Hubs.d.ts file it generated:

    interface SignalR {
        chatHub : ChatHub;
    }

    interface ChatHub {
        server : ChatHubServer;
        client : any;
    }

    interface ChatHubServer {
        send(name : string, message : string) : JQueryPromise;
    }

Not bad! It gave us a new type, ChatHub (we were lazy in our handmade version, and made an anonymous type, one of TypeScript’s nice features), as well as a ChatHubServer type, which shows the signature for the send() function. Great! Any time we create a new function on the server, we can re-run this template and automatically get support for calling it from our TypeScript code.

But what about the client? It’s defined as “any”, with no help about the addNewMessageToPage() method we have. The reason that Hubs.tt can’t help us (yet) is that on the C# side, we have the following in Hubs/ChatHub.cs:

        Clients.All.addNewMessageToPage(name, message);

But the Clients.All object is marked as “dynamic”, much like everything in JavaScript is. This allows the C# code to compile, but at runtime SignalR takes the addNewMessageToPage() call, and just assumes that that’s going to mean something to the client, and creates a call based on that assumption. This isn’t enough information for Hubs.tt to be able to create some type information.

However, if you look in the Hubs.tt file (even if you know nothing about T4 Templates, as I didn’t), you’ll find

    var hubType = hub.HubType;
    string clientContractName = hubType.Namespace + ".I" + hubType.Name + "Client";
    var clientType = hubType.Assembly.GetType(clientContractName);

and

    client : <#= clientType != null?(hubType.Name+"Client"):"any"#>;

from this, and a little hint from the comments, we discover that if we define an interface in the same namespace as our ChatHub, with the name IChatHubClient, we can provide the type information that matches what we want implemented on the client side. I’ll note again that this class must be in the same namespace as the server, which you can see from the code above with the “hubType.Namespace” reference; I wasted a bit of time by making a nice Client folder and putting the interface definition in there, which by default then had a SignalRChat.Client namespace…

For now, instead of being fancy and making a separate place for our client interface definitions, let’s just add it to our ChatHub.cs file, where we’re using it:

    public interface IChatHubClient
    {
        void addNewMessageToPage(string name, string message);
    }

Re-running the Hubs.tt template (by right-clicking it and selecting Run Custom Tool), we now get (with comments removed):

    interface SignalR {
        chatHub : ChatHub;
    }

    interface ChatHub {
        server : ChatHubServer;
        client : ChatHubClient;
    }

    interface ChatHubServer {
        send(name : string, message : string) : JQueryPromise;
    }

    interface ChatHubClient
    {
        addNewMessageToPage : (name : string, message : string) => void;
    }

Now TypeScript can check our client functions, or server calls, and even the parameters we accept and pass.

Beyond the trivial

And just to show that we can use more complex types, that we’re not limited to simple function parameters, we can modify our interface to bundle the username and the message into a single class.

    public class ChatMessage
    {
        public string Name { get; set; }
        public string Message { get; set; }
    }

    public class ChatHub : Hub
    {
        public void Send(ChatMessage msg)
        {
            // Call the addNewMessageToPage method to update clients.
            Clients.All.addNewMessageToPage(msg);
        }
    }

    public interface IChatHubClient
    {
        void addNewMessageToPage(ChatMessage msg);
    }

and our client-side code to

    $(function () {
    // Reference the auto-generated proxy for the hub.
    var chat = $.connection.chatHub;
    // Create a function that the hub can call back to display messages.
    chat.client.addNewMessageToPage = function (message:ChatMessage) {
        // Add the message to the page.
        $('#discussion').append('<li><strong>' + htmlEncode(message.Name)
            + '</strong>: ' + htmlEncode(message.Message) + '</li>');
    };
    // Get the user name and store it to prepend to messages.
    $('#displayname').val(prompt('Enter your name:', ''));
    // Set initial focus to message input box.
    $('#message').focus();
    // Start the connection.
    $.connection.hub.start().done(function () {
        $('#sendmessage').click(function () {
            // Call the Send method on the hub.
            //chat.server.send($('#displayname').val(), $('#message').val());
            chat.server.send({
                Name: $('#displayname').val(),
                Message: $('#message').val()
            });
            // Clear text box and reset focus for next comment.
            $('#message').val('').focus();
        });
    });
    });

Note that we finally added a little typing to our TypeScript by defining addNewMessageToPage() as a function that takes message:ChatMessage .

Now when we re-run our template, we get

    interface SignalR {
        chatHub : ChatHub;
    }

    interface ChatHub {
        server : ChatHubServer;
        client : ChatHubClient;
    }

    interface ChatHubServer {
        send(msg : ChatMessage) : JQueryPromise;
    }

    interface ChatHubClient
    {
        addNewMessageToPage : (msg : ChatMessage) => void;
    }

    ////////////////////
    // Data Contracts //
    ////////////////////
    //#region data contracts

    /**
      * Data contract for SignalRChat.ChatMessage
      */
    interface ChatMessage {
        Name : string;
        Message : string;
    }

There we see that our client and server functions now have the new type being used as a parameter, and it’s defined at the bottom of the file to allow TypeScript to check its use.

Limitations

Unfortunately, if you’ve been trying to play along, you might have hit a little snag when modifying the interface in this last part. There’s a chicken-and-egg thing going on here, where the T4 Template will not generate new content if the project doesn’t compile, and the project can’t compile because it doesn’t have the new Hubs.d.ts to match the changes we’ve made — including changes to the code that Hubs.tt needs to look at! Hubs.tt doesn’t look at the source code, but rather at the final DLL generated by the compilation process, using Reflection; this is why the project needs to compile for Hubs.tt to work.

The way around the problem is to make changes in an orderly fashion:

  • Modify the C# code, changing parameter names/types as needed. Because the TypeScript code still has the old Hubs.d.ts, it’s happy, and the C# code doesn’t care about anything else, so it’s happy, so you can now compile the project.
  • Re-run the T4 Template. Now that there’s a new DLL, Hubs.tt can run against it, find the new server-side and client-side changes, new classes, etc. and generates a new Hubs.d.ts. At this point, errors will likely appear, now that the TypeScript code doesn’t match the interfaces defined in the Hubs.d.ts file.
  • Modify the TypeScript code: update your client functions as defined back in the IChatHubClient interface; modify parameters, and add code that uses the now-defined classes (like ChatMessage). Once these changes are made, you can again compile the project, and everything should be in-sync.

While this isn’t an insurmountable issue, it can definitely be a problem in that affects your workflow, if you want to change the server- and client-side code simultaneously. If you do so, the only way to get out of the chicken-and-egg is to

  • Delete the Hubs.d.ts file. This is an auto-generated file, so you should always feel comfortable deleting it. This will cause compilation errors, because TypeScript now knows nothing about your SignalR functions and classes.
  • Add the
        interface SignalR {
            chatHub: any;
        }

    at the top of your TypeScript, like we did early on. This will hush TypeScript as it did before, allowing the project to compile.

  • Re-run the T4 Template. This will generate a correct Hubs.d.ts file, but you’ll now have an error because chatHub is defined twice (as we saw before.) We now remove our stop-gap from the previous step, and recompile.

Another unfortunate limitation is that we cannot take advantage of our type information to help out on the C# side. In our Hubs/ChatHub.cs , we have

    public void Send(ChatMessage msg)
    {
        // Call the addNewMessageToPage method to update clients.
        Clients.All.addNewMessageToPage(msg);
    }

but as we said earlier, Clients.All is a dynamic object, so there’s no check here that addNewMessageToPage() exists, even with the

    public interface IChatHubClient
    {
        void addNewMessageToPage(ChatMessage msg);
    }

defined right below it. It would fantastic to be able to using typecasting, such as

    ((IChatHubClient)Clients.All).addNewMessageToPage(msg);

or

    (Clients.All as IChatHubClient).addNewMessageToPage(msg);

and indeed, doing either of these will help discover any problems with our code: it’ll ensure that the function is defined as the client expects, and that the parameter(s) are correct. Unfortunately, the act of casting that dynamic object changes it, and kills the actual functionality behind-the-scenes that SignalR has.

I think there’s likely some way, with conditional compilation flags and such, that I can write the code to give me the Intellisense checking but stop short of actually casting it at compilation. I’ve not spent any time looking into it, but probably will shortly.

Conclusion

While I’ve only scraped the surface of what SignalR and TypeScript can each do, I hope that I’ve shown that when put together, they can allow large-scale, powerful applications, without a lot of the work of building your own client/server communications, or falling afoul of common JavaScript coding problems.