Controlling the lifetime of a connection

SignalR lets us control several aspects of the connection lifetime on both the server and the client side. SignalR has the goal of delivering a simple experience based on the idea of a persistent connection, but we cannot really rely on the assumption that it will always be available, because too many factors may interfere with it. While it's true that SignalR does its best to isolate us from these kind of problems, thanks to smart connection strategies and recovery procedures, on the other hand we might need to have finer control over what's happening behind the scenes, and we might want to handle the anomalies and the reconnection procedures ourselves.

In this first recipe of the chapter, we'll use a Hub to illustrate how we can monitor when the connection state changes over time. Later, we will also write a simple JavaScript client in order to show how we can perform the same kind of checks on the client side.

Getting ready

Before writing the code of this recipe, we need to create a new empty web application project, which we'll call Recipe30.

How to do it…

Let's build our project, concentrating first on the server-side code, using the following steps:

  1. We first add a new empty Hub by navigating to Add | New Item… on the context menu of the project in the Solution Explorer window, and from there looking for the SignalR Hub Class (v2) template to create our EchoHub class. We just want to monitor the connection status and therefore will not need any specific method on our Hub. Let's make it look like the following code:
    using System.Diagnostics;
    using System.Threading.Tasks;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    
    namespace Recipe30
    {
        [HubName("echo")]
        public class EchoHub : Hub
        {
        }    
    } 
  2. We then need, as usual, a Startup class, which we will create in the same way we did so far for most of the recipes. However, this time, we will specify a set of useful parameters inside the Configuration() method, as shown in the following code:
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                GlobalHost.Configuration.DisconnectTimeout =TimeSpan.FromSeconds(30);
                GlobalHost.Configuration.ConnectionTimeout =TimeSpan.FromSeconds(110);
                GlobalHost.Configuration.KeepAlive =TimeSpan.FromSeconds(10);
    
                app.MapSignalR();
            }
        }

    The GlobalHost object exposes a Configuration member, through which we can change some default settings that influence how the connection handling is performed (the values in the sample code are the current defaults defined by SignalR). The values are described as follows:

    • DisconnectTimeout: This asks SignalR to wait a maximum number of seconds after a transport connection is lost before raising a Disconnected event, and terminating the failed logical connection
    • ConnectionTimeout: In case long polling is used, transport connections will be kept around for a maximum number of seconds waiting for a response; when that timeout expires with no activity, the client will be forced to open a new connection
    • KeepAlive: For transports other than long polling, this regulates how often a keepalive packet is sent to avoid the underlying connection being dropped because of inactivity; this value must be no more than one-third of the DisconnectTimeout value, and SignalR automatically adjusts this value accordingly to this rule if DisconnectTimeout is specified without setting KeepAlive explicitly afterwards
  3. Let's go back to our EchoHub class to override a set of methods exposed by any Hub-derived type. Those methods will notify us about specific logical events happening on the connection. When we say logical event, we are talking in the context of the level of abstraction defined by SignalR on top of the actual events occurring at the transport or physical level on the network. Let's look at the following code:
            public override Task OnConnected()
            {
                Trace.WriteLine(string.Format("Connected: {0}",Context.ConnectionId));
                return base.OnConnected();
            }
            public override Task OnDisconnected()
            {
                Trace.WriteLine(string.Format("Disconnected: {0}", Context.ConnectionId));
                return base.OnDisconnected();
            }
            public override Task OnReconnected()
            {
                Trace.WriteLine(string.Format("Reconnected: {0}",Context.ConnectionId));
                return base.OnReconnected();
            }

    As we can see, there are three methods that can be overriddenRedundant:

    • OnConnected(): This is triggered only the first time a connection is established, and a new connection ID is assigned to the calling client
    • OnReconnected(): This can be triggered several times, once each time a connection is reestablished by SignalR after a lower-level network disconnection, and this happens every time SignalR is able to rebuild the same logical connection on top of a potentially new underlying one; in this case, the connection ID does not change
    • OnDisconnected(): This is triggered when a low-level disconnection occurs and SignalR is not able to establish it again before the DisconnectTimeout interval has expired; the logical connection is dropped, and in order to contact the server again, the client will have to start a new connection, thus receiving a new connection ID

    Inside our method implementations, we just trace the name of the event and the connection identifier involved, and then call the base implementation.

    Let's now build a client we will use to test our code. Both the JavaScript (the one we are going to use) and the .NET client libraries have similar connection control features. In our code, we will use them to perform the same kind of checks we've been performing on the server side.

  4. We add an HTML page called index.html, which we'll use to build our client, and where we'll reference the usual JavaScript files that have already been added to the project when the Microsoft ASP.NET SignalR package has been referenced by adding EchoHub. Somewhere in the page body, we'll also add a button that we'll use to toggle a connection to the server and test our control code, as shown in the following code:
    <script src="Scripts/jquery-2.1.0.js"></script>
    <script src="Scripts/jquery.signalR-2.0.2.js"></script>
    <script src="/signalr/hubs"></script>
    ...
    <button id="toggle" disabled="true">Toggle connection</button>
  5. We are ready to write our client code inside a script block, initializing a couple of useful reference variables as follows:
        <script>
            $(function() {
    
                var hub = $.connection.hub,
                    echo = $.connection.echo;
                
                ...
                
            });
        </script>
  6. On the hub variable, we can supply a set of callback functions to be called when specific connection events happen:
            
    hub.starting(function () {
        console.log('starting'), //no hub.id yet!
    });
    hub.reconnecting(function () {
        console.log('reconnecting: ' + hub.id);
    });
    hub.reconnected(function () {
        console.log('reconnected: ' + hub.id);
    });	
    hub.disconnected(function () {
        console.log('disconnected: ' + hub.id);
    });
    
    ...

    The names and their corresponding meanings are straightforward. Because a connection is always initiated by the client, we have a starting event and a reconnecting event available, which are client-only events with no matching counterpart on the server side.

  7. We can also supply a callback to be called whenever a change in the connection state happens, as depicted in the following code:
    hub.stateChanged(function (state) {
        console.log('stateChanged: ' + hub.id);
        console.log(state);
        $("#toggle").attr('disabled',state.newState != 
               $.signalR.connectionState.connected &&state.newState != 
               $.signalR.connectionState.disconnected);
    });
    
    ...

    In the callback supplied to the stateChanged() method, we can output the current state whenever it changes, and we enable or disable the toggle button accordingly. The possible connection states are defined as properties exposed by the $.signalR.connectionState member declared inside the referenced jQuery.signalR-(version).js file.

  8. Now we do quite a curious thing. Have a look at the following code:
    echo.client.dummy = function() { };
    
       ...

    Why this? In this example, we are using the Hubs API with dynamic proxies because it's the most common, simple, and straightforward way to use SignalR. However, as of today, the previously mentioned server-side events are not generated if there are no client-side callbacks available. We need to have at least one of them in place, even if we will never call it; that's why we added the dummy one. It's worth making clear that, although it's something that we do on the client side, this is not necessary to make the client-side events work. It's there to make the server-side ones work, and there's no need to call that method from the server at all. Of course, if you already have proper client-side callbacks to be invoked from the server side, those will be just fine with no need for a dummy one, and the client-side events will work just fine. It's a curious and quite strange thing to do, but that's how it currently works and it might be improved in future versions.

  9. Let's finalize our client-side portion with the following code, which will toggle the connection on or off each time a button called toggle is clicked on:
    var starter = function() {
        return hub
        .start()
        .done(function () {
            console.log('connected: ' +hub.id);
        });
    };
    
    $("#toggle").click(function () {
        $("#toggle").attr('disabled', true);
        if ($.signalR.connectionState.disconnected ==
            echo.connection.state)
            starter();
        else if ($.signalR.connectionState.connected ==
            echo.connection.state)
            hub.stop();
    });
    
    starter();

It's interesting to observe the usage of the stop() member exposed by hub to explicitly and gracefully end a connection. We never explicitly mentioned it before because it's quite a rare thing to do, but it's good to know that it's possible in case you need it.

We are ready to test the code that we just wrote by launching the application and navigating to the index.html page. Each time we'll click on the toggle connection button, we'll alternatively turn the connection on and off, and these actions will trigger the client- and server-side events that we hooked into with our code. It will be interesting for you to try breaking the network connection between the browser and the web server in any way you may want too, and observe the reconnection events happening automatically according to the timeout values we have set earlier.

How it works…

What happens under the hood is pretty smart and complex, and the details are out of the scope of this book. That said, it's important to understand that each time a persistent connection is created, SignalR tries its best to keep it alive despite any possible lower-level network disruption, hiding these kind of problems from us. Whenever these attempts fail and SignalR decides that the connection is lost, it will not try to fix it anymore; the only way for the client to connect again will be to perform a full connection restart, hence generating a new connection ID. This is one of the reasons why the action of associating any state information to a connection identifier, which is one of the most common needs, it's definitely possible but should be done in full awareness of these implications. You will have to write defensive code that is aware of the fact that the connection ID might go away anytime.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset