Monday, February 13, 2012

SignalR: My journey into .NET Server-to-Client communications - first bumps on the road

SignalR itself says that it's an async signalling library for .NET, but I see it as a very convenient way to call JS functions from server-side and server-side functions from JS.  The official page is here. I have first read about this library in Scott Hanselman's blog and as I'm now starting a new hobby-project, I've decided to try it out.

SignalR has its NuGet package, so I've installed it from there.

Before moving to anything more complex, I wanted to create a simple, "proof-of-concept", where the client just sends a message to the server, when the page is loaded and server replies back with some initial set of data. Again, for this initial phase, I'm trying to keep things simple, so I'm using Hubs. My initial hub is here:
    [HubName("myHub")]
    public class MyHub: Hub
    {
        public void Register()
        { 
            var result = new Order[] {
                new Order
                {
                    OrderId = 1,
                    Product= "lorem"
                },
                new Order
                {
                    OrderId = 2,
                    Product = "ipsum"
                }
            };
            Caller.registerCallback(result);
        }
    }
Now, here it's all pretty simple: we inherit the class from SignalR.Hubs.Hub, to indicate that it's a hub. A hub is the class, which will receive communications from the client side. We're also decorating it with HubName attribute, which specifies the name of our hub, which we'll use in JS and the ending of our url for cummunications.

The class contains a single method - Register (I could have chosen any name I want) which will be called from the client side and return a hard-coded list of orders via a client callback. See the Caller property, that I'm using to invoke the registerCallback? (that's the name of the function, which we'll define in JS, which will handle our results) It is used to communicate only with the client, which just invoked our method - and it's all automatic :)

Next comes our client side part:

var hub;
$(document).ready(function () {
     hub = $.connection.myHub;

     hub.addOrder = function (addOrderMessage) {
        $(".orderList").append("<li id='" + addOrderMessage.OrderId + "'>" + addOrderMessage.Product+ "</li>");
    };

    hub.registerCallback = function (initialListOfOrders) {
        for (var i = 0; i < initialListOfOrders.length; i++) {
            var order = initialListOfOrders[i];
            hub.addOrder(order);
        }
    };

    $.connection.hub.start(function () { hub.register(); });
});
Again, code is simple enogh - after the page is loaded, we're assigning our hub to a local variable. Then, we're defining two functions on our hub (which both can be invoked from the server side) - addOrder, which takes a single order and adds it to the list and registerCallback, which gets initial list of order.
And on the last line, we're starting the communication process. And that's where I initially hit my first bump.
You see, the example in Scott's blog post starts the hub without passing any function to the start method. Some other exmaples did the same. So initially the ending of my script looked like this:

    ...
    $.connection.hub.start();
    hub.register();

But instead of registering with the server, script threw an exception on hub.register, which stated that:
SignalR: Connection must be started before data can be sent. Call .start() before .send()
Which was rather confusing, given that I was calling my register() method just after calling hub.start(). To make things worse, all the other examples I had, were working perfectly, so I rejected the idea that something is missing in my hosting environment. And it seemed, that nobody else was seeing this issue.
And then I realized - Scott's example isn't calling anything on the hub immediately after it is started - his app only calls hub method on events from the user - and that gives the hub plenty of time to initialize. So after some more googling, I've found, that hub.start() has a callback, which is invoked after the hub is initialized.

Now, you're probably wondering, how does my JS code know about this $.connection.myHub, which I've declared on the server-side? In order for this to work, I need to include two JS files:

<script src="@Url.Content("~/Scripts/jquery.signalR.js")" type="text/javascript"></script>
<script src="@Url.Content("~/signalr/hubs")" type="text/javascript"></script>

The first one is the signalR JQuery plugin. And the second one.... if you'll check your source folder, you won't find anything like it, but it's generated at the runtime by SignalR and includes all the hubs you've delcared.

So that's it for my first post (hopefully - out of many) about SignalR. So far, I'm really happy with this library, as it provides a very simple way for .NET developers to get a very smooth user experience.

No comments:

Post a Comment