SignalR Realtime Application Cookbook
上QQ阅读APP看书,第一时间看更新

Calling back the caller from a Hub's method

With this recipe, we are actually starting to look at more interesting and real-time SignalR features. We will see how a Hub can call back into a client that just performed a remote method call.

Getting ready

Before starting with this recipe, we need to create a new empty web application, which we'll call Recipe06.

How to do it…

Let's start building the server-side portion of this recipe using the following steps:

  1. We must first add a Hub called EchoHub.
  2. We must then add an OWIN Startup class named Startup, containing just a simple app.MapSignalR(); bootstrap call inside the Configuration() method.
  3. Let's make the Hub's content look like the following:
    using System;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    
    namespace Recipe06
    {
        [HubName("echo")]
        public class EchoHub : Hub
        {
            public void Hello()
            {
                var msg = string.Format("Greetings {0}, it's
                    {1:F}!",
                    Context.ConnectionId, 
                    DateTime.Now);
                var caller = Clients.Caller;
                caller.greetings(msg);
            }
        }
    }

What's important here? We need to make sure that the following steps are performed:

  • Inside the Hello() method, we build a message (msg) using the current time and a property exposed by the Context member of the Hub type called ConnectionId. This unique identifier is automatically assigned by SignalR to every new connection. Each connection belongs to a specific connected client, and we can assume for now that such a connection stays around until the client goes away. Things are actually more complex than this, but we'll talk about it in future recipes.
  • Context is of type HubCallerContext, and exposes a set of properties related to the current connection, such as the just mentioned ConnectionId, query string parameters, cookies, and more.
  • When msg is ready, we use the Clients member from the Hub type, which gives us access to the universe of the connected clients. From there, we take a reference to the Caller member, representing the calling client from which the Hello() method has been contacted.
  • We finally call the greetings() method on the caller variable, supplying the msg we just built. We expect this call to be performed on the caller from which the server-side Hello() method has been hit.

This code illustrates how we can use Context to access information about the connection (we can not only find details about the SignalR connection identifier, but also about HTTP headers or the query string), and how to access the current caller from it.

The other interesting part about caller is that it's a dynamic object, meaning we can try to call whatever method we want on it as the code will pass any compile-time check and the call will be resolved at runtime by SignalR. The call will succeed only if a method with the same name and expecting the same parameters is found by SignalR on the client-side hub proxy, no matter which client library we are using (in this case, the JavaScript one), otherwise it will silently fail. You can get more information about dynamic programming in .NET at http://msdn.microsoft.com/en-us/magazine/gg598922.aspx, while you can get specific introduction about a dynamic method resolution strategy at http://msdn.microsoft.com/library/ee658247.

Finally, the execution of the dynamic call is performed asynchronously. The server-side code will not be blocked while waiting for the actual execution of the code on the client; instead, it will exit immediately, relinquishing the control of the code flow back to the caller. Asynchronism is a very important feature of SignalR. Every time there is any code that implies network activity (connection-related operations, client-to-server calls, and server-to-client calls), the execution of that code is asynchronous. This introduces some complexity, but it's an absolutely necessary feature in order to achieve the astonishing performance characteristics of this library.

The whole asynchronous strategy in SignalR is built around the Task-based Asynchronous Pattern (TAP). You can get an overview about it at http://msdn.microsoft.com/en-us/library/hh873175(v=vs.110).aspx.

Let's now create an HTML client page called index.html, which contains the following code:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Recipe06</title>
    <script src="Scripts/jquery-2.1.0.js"></script>
    <script src="Scripts/jquery.signalR-2.0.2.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
        $(function () {
            var hub = $.connection.echo;

            hub.client.greetings = function (message) {
                $('#message').html(message);
            };

            $.connection.hub
                .start()
                .done(function () {
                    $('#send').click(function () {
                        hub.server.hello();
                    });
                });
        });
    </script>

</head>
<body>
    <button id="send">Say Hello!</button>
    <p id="message"></p>
</body>
</html>

What's important here? We need to make sure that we perform the following steps:

  • We have the usual references to the jQuery and SignalR libraries, and to the SignalR dynamic hubs endpoint, which from now on we'll take for granted.
  • We take a reference to the echo hub, and from there we access the client member to extend it with a function called greetings(), whose name and signature are matching the method that the Hub is trying to call back; the function will just update a p element on the page with the received message.
  • We then start the connection, and when it's ready (the done callback), we hook the click event of the send button to the action of calling the Hello() method on the echo hub proxy through the server member; more detail about starting a connection can be found in Chapter 3, Using the JavaScript Hubs Client API.
  • It's important that the client member is filled with any callback function before starting the connection.
  • We decide to hook the click event on the button just inside the done callback to make sure we cannot call the Hello() method before the connection being established.

Now run the application, navigate to index.html and click on the Say Hello! button. We should see something like what is shown in the following screenshot:

How to do it…

Every time we click on the Say Hello! button, we'll see that the time changes, but not ConnectionId, and the page is not refreshed. On the other hand, every time we do a full refresh of the page and click on the button, we'll see a new ConnectionId displayed.

How it works…

When we ask SignalR to open a connection from the client, it gives us a persistent connection, the illusion that a permanent channel has been established between the client and the server. We can use this channel to send messages from the client to the server and the other way round. Using a Hub we can do that through an object-oriented API, which makes the connection transparent to both parts.

How the connection is physically established is a SignalR concern; it will use the best option available according to the context of the running code. However, it could well be that behind the scenes the connection is not permanent at all. We just don't have to worry about that. We'll talk more about these details in Chapter 3, Using the JavaScript Hubs Client API and Chapter 4, Using the .NET Hubs Client API.

The actual resolution of the method calls, both on the server-side Hub and on the connected clients, is actually performed by SignalR dynamically at runtime. On the server side, SignalR leverages the .NET dynamic support to do so, while on the client side, this depends on the library used. In the case of this recipe, we are using the JavaScript client library, so the actual support for dynamic objects and calls is built into the language.