Using a Persistent Connection

0
163
16 min read

(For more resources related to this topic, see here.)

In our journey through all the features exposed by SignalR, so far we have been analyzing and dissecting how we can use the Hubs API to deliver real-time, bidirectional messaging inside our applications. The Hubs API offers a high-level abstraction over the underlying connection, effectively introducing a Remote Procedure Call (RPC) model on top it. SignalR is also offering a low-level API, which is the foundation Hubs is built on top of. However, it’s also available to be used directly from inside our applications and it’s called the Persistent Connection API.

A Persistent Connection API is a more basic representation of what the concrete network connection really is, and exposes a simpler and rawer API to interact with. We hook into this API by inheriting from a base class (PersistentConnection) in order to add our own custom Persistent Connection manager. It is the same approach we have been using when defining our custom Hubs by deriving from the Hub class. Through a registration step, we’ll then let SignalR know about our custom type in order to expose it to our clients.

The PersistentConnection type exposes a few properties supplying entry points from where we send messages to the connected clients, and a set of overridable methods we can use to hook into the connection and messaging pipeline in order to handle the incoming traffic. This article will be an introduction to the Persistent Connection API, hence we’ll be looking at just a portion of it in order to deliver some basic functionalities. We will also try to highlight what the peculiar attributes of this API are, which characterize it as a low-level one—still delivering powerful features, but without some of the nice mechanisms we have for free when using the Hubs API.

For the next four recipes, we will always start with empty web application projects, and we’ll use simple HTML pages for the client portions. However, for the last one, we’ll be building a console application.

Adding and registering a persistent connection

In this first recipe of the article, we’ll introduce the PersistentConnection type, and we’ll go into the details about how it works, how to use it, and how to register it into SignalR to expose it to the connected clients. Our goal for the sample code is to trace any attempt of connection from a client towards the exposed endpoint. We will also write a minimal JavaScript client in order to actually test its behavior.

Getting ready

Before writing the code of this recipe, we need to create a new empty web application that we’ll call Recipe25.

How to do it…

Let’s run through the steps necessary to accomplish our goal:

  1. We first need to add a new class derived from PersistentConnection. We can navigate to Add | New Item… in the context menu of the project from the Solution Explorer window, and from there look for the SignalR Persistent Connection Class (v2) template and use it to create our custom type, which we’ll name EchoConnection. Visual Studio will create the related file for us, adding some plumbing code in it. For our recipe, we want to keep it simple and see how things work step by step, so we’ll remove both methods added by the wizard and leave the code as follows:

    using Microsoft.AspNet.SignalR; namespace Recipe25 { public class EchoConnection : PersistentConnection { } }

    We could reach the same result manually, but in that case we would have to take care to add the necessary package references from NuGet first. The simplest way in this case would be to reference the Microsoft ASP.NET SignalR package, which will then bring down any other related and necessary module. The process of doing that has already been described earlier, and it should already be well known. With that reference in place, we could then add our class, starting with a simple empty file, with no need for any wizard.

  2. We then need to add an OWIN Startup class and set it up so that the Configuration() method calls app.MapSignalR<…>(…); in order to properly initiate the persistent connection class we just added. We should end up with a Startup.cs file that looks like the following code:

    using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(Recipe25.Startup))] namespace Recipe25 { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR<EchoConnection>("/echo"); } } }

    The usual Configuration() method calls MapSignalR(), but this time it uses a different overload, supplying EchoConnection as the type of our connection class and /echo as the name of the related endpoint. Behind the scenes, what MapSignalR() does is quite similar to what was happening when we were using the Hubs API; however, in this case, there is a little bit less magic because we have to explicitly specify both the type of the connection class and the URL from where it will be exposed. If we had more than one connection class to expose, we would have to call the MapSignalR() method once for each of them, and we would have to specify different URL addresses each time. It’s worth noting that the endpoint name must begin with a / character. If we omit it, SignalR will raise an exception.

  3. Let’s go back to our EchoConnection class and write some code to be executed when a new connection is performed. In order to achieve that, we have to override the OnConnected() method from PersistentConnection as shown in the following code:

    protected override Task OnConnected(IRequest request, string connectionId) { System.Diagnostics.Trace.WriteLine("Connected"); return null; }

    OnConnected() is one of the overridable methods mentioned in the Introduction section of the article, and its role is to give us a point from where we can handle every incoming connection.

    The first argument of the method is an instance of an object implementing the IRequest interface, which is a SignalR interface whose role is to describe the HTTP incoming request. It’s similar to the very well-known ASP.NET Request object, but this version is much lighter and focused on what’s needed in the context of SignalR, without exposing anything that could harm its efficiency, such as the (somewhat infamous) Session object or a read-write Cookies collection.

    The second argument is of type string and contains the connection identifier generated during the handshaking steps performed while setting up the connection.

    OnConnected() returns a Task instance, and in this way it’s declaring its asynchronous nature. But, in our case, we do not really have anything special to send back to its caller, so finalizing the code of the method with return null; will be just fine.

    The only relevant line of code in our method is simply sending a diagnostic message to any Trace listener available.

    We are done, and it’s been pretty straightforward. Now any client hitting the /echo endpoint will be handled by this class, and at every new connection, the OnConnect() method will be called. When we say new connection, we actually mean whenever SignalR performs a full handshaking cycle, selecting and applying a connection strategy. If a specific client (for example, a browser window) loses its connection and is able to reconnect just after the reconnection retry timeout has expired, a new full-blown connection will be built, a new connectionId value will be assigned to it and OnConnect() will be called again.

    We need a client to test if everything is working properly. To achieve this goal, let’s add an HTML page called index.html, which we’ll use to build our client. In this page, we’ll link all the necessary JavaScript files that have already been added to the project when the Microsoft ASP.NET SignalR package has been referenced. Let’s proceed.

  4. We first reference jQuery using the following code:

    <script src = "Scripts/jquery-2.1.0.js"></script>

  5. Then we need to link the SignalR JavaScript library with the following line of code:

    <script src = "Scripts/jquery.signalR-2.0.2.js"></script>

    At this point, we used to add a reference to the dynamic hubs endpoint in the previous recipes, but this is not the case here. We do not need it because we are not using the Hubs API, and therefore we don’t need dynamic proxies that are typical of that way of using SignalR. The SignalR JavaScript library that we just added contains all that’s necessary to use the Persistent Connection API.

  6. We finally add a simple script to connect to the server as follows:

    <script> $(function() { var c = $.connection('echo'); c.start() .done(function(x) { console.log(c); console.log(x); //x and c are the same! }); }); </script>

    Here we first interact with the $.connection member we already saw when describing the Hubs API, but in this case we do not use it to access the hubs property. Instead, we use it as a function to ask SignalR to create a connection object pointing at the endpoint we specify as its only argument (echo). The returned object has a similar role to the one the hubs member has; in fact, we can call start() on it to launch the actual connection process, and the returned value is again a JavaScript promise object whose completion we can wait for, thanks to the done() method, in order to ensure that a connection has been fully established. Our simple code prints out both the connection object we obtained (the c variable) and the argument supplied to the done() callback (the x variable) just to show that they are actually the same object, and we are free to pick any of them when a connection reference is needed.

To test what we did, we just need to run the project from Visual Studio and open the index.html page using the Developer Tools of the browser of choice to check the messages printed on the browser console.

How it works…

The SignalR $.connection object exposes the low-level connection object, which is the real subject of this article. Its server-side counterpart can be any type derived from PersistentConnection that has been previously registered under the same endpoint address targeted when calling the $.connection() function. Any major event happening on the connection is then exposed on the server by events that can be intercepted by overriding a bunch of protected methods. In this recipe, we saw how we can be notified about new connections just by overriding the OnConnected() method. We’ll see a few more of those in future recipes.

The rest of the client-side code is very simple, and it’s very similar to what we have been doing when starting connections with the Hubs API; the only big difference so far is the fact that we do not use the dynamically generated hubs member anymore.

Sending messages from the server

Now that we have a good idea about how PersistentConnection can be registered and then used in our applications, let’s go deeper and start sending some data with it. As usual, in SignalR, we need a client and a server to establish a connection, and both parts can send and receive data. Here we’ll see how a server can push messages to any connected client, and we’ll analyze what a message looks like.

We already mentioned the fact that any communication from client to server could, of course, be performed using plain old HTTP, but pushing information from a server to any connected client without any specific client request for that data is not normally possible, and that’s where SignalR really helps.

We’ll also start appreciating the fact that this API stands at a lower level when compared to the Hubs API because its features are more basic, but at the same time we’ll see that it’s still a very useful, powerful, and easy-to-use API.

Getting ready

Before writing the code for this recipe, we need to create a new empty web application, which we’ll call Recipe26.

How to do it…

The following are the steps to build the server part:

  1. We first need to add a new class derived from PersistentConnection, which we’ll name EchoConnection. We can go back to the previous recipe to see the options we have to accomplish that, always paying attention to every detail in order to have the right package references in place. We want to end up with an EchoConnection.cs file containing an empty class having the following code:

    using Microsoft.AspNet.SignalR; namespace Recipe26 { public class EchoConnection : PersistentConnection { } }

  2. We then need to add the usual OWIN Startup class and set it up so that the Configuration() method calls app.MapSignalR<EchoConnection> in order to properly wire the persistent connection class we just added (“/echo”); to our project. This is actually the same step we took in the previous recipe, and we will be doing the same through the remainder of this article. The final code will look like the following:

    using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(Recipe26.Startup))] namespace Recipe26 { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR<EchoConnection>("/echo"); } } }

  3. Back to our EchoConnection class, we want to redefine the OnConnected() overridable method, whose function we already described earlier, and from there send a message to the client that just connected. This is as simple as coding the following:

    protected override Task OnConnected(IRequest request, string connectionId) { return Connection.Send(connectionId, "Welcome!"); }

    The PersistentConnection class exposes several useful members, and one of the most important is for sure the Connection property, of type IConnection, which returns an object that allows us to communicate with the connected clients. A bunch of methods on the IConnection interface let us send data to specific sets of connections (Send()), or target all the connected clients at the same time (Broadcast()). In our example, we use the simplest overload of Send() to push a string payload down to the client that just connected. Both Send() and Broadcast() run asynchronously and return a Task instance, which we can directly use as the return value of the OnConnect() method.

    Another relevant member inherited from PersistentConnection is the Groups member, which exposes a couple of Send() overloads to push messages down to connections belonging to a specific group. Groups also exposes a set of capabilities to manage the members of specific groups the same way the Hubs API does. There is no conceptual difference here with what we explained earlier, therefore we’ll skip any further explanation about it.

    All the methods we just mentioned expect a last parameter of type object, which is the actual payload of the call. This value is automatically serialized before going on the wire to reach the clients. When there, the involved client library will deserialize it, giving it back its original shape using the best data type available according to the actual client library used (JavaScript or .NET). In our example, we used a simple string type, but any serializable type would do, even an anonymous type.

    Back to this sample, any client connecting to the /echo endpoint will be handled by the OnConnect() method exposed by EchoConnection, whose body will send back the message Welcome! using the Send() call.

Let’s build an HTML page called index.html to host our client code. In this page, we’ll link all the necessary JavaScript files as we already did in the previous recipe, and then we’ll add a few lines of code to enable the client to receive and log whatever the server sends.

  1. We first reference jQuery and SignalR with the following code:

    <script src = "Scripts/jquery-2.1.0.js"></script> <script src = "Scripts/jquery.signalR-2.0.2.js"></script>

  2. We then add the following simple piece of script:

    <script> $(function () { var c = $.connection('echo'); c.received(function(message) { console.log(message); }) .start(); }); </script>

    Here, we first interact with the $.connection member in the same way we did in the previous recipe, in order to create a connection object towards the endpoint we specify as its argument (echo). We can then call start() on the returned object to perform the actual connection, and again the returned value is a JavaScript promise object whose completion we could wait for in order to ensure that a connection has been fully established. Before starting up the connection, we use the received() method to set up a callback that SignalR will trigger whenever a message will be pushed from the type derived from PersistentConnection down to this client, regardless of the actual method used on the server side (Send() or Broadcast()). Our sample code will just log the received string onto the console. We’ll dig more into the received() method later in this article.

We can test our code by running the project from Visual Studio and opening the index.html page using the Developer Tools of the browser to see the received message printed onto the console.

How it works…

Any type derived from PersistentConnection that has been correctly registered behind an endpoint can be used to send and receive messages. The underlying established connection is used to move bits back and forth, leveraging a low-level and highly optimized application protocol used by SignalR to correctly represent every call, regardless of the underlying networking protocol. As usual, everything happens in an asynchronous manner.

We have been mentioning the fact that this is a low-level API compared to Hubs; the reason is that we don’t have a concept of methods that we can call on both sides. In fact, what we are allowed to exchange are just data structures; we can do that using a pair of general-purpose methods to send and receive them. We are missing a higher abstraction that will allow us to give more semantic meaning to what we exchange, and we have to add that ourselves in order to coordinate any complex custom workflow we want to implement across the involved parts. Nevertheless, this is the only big difference between the two APIs, and almost every other characteristic or capability is exposed by both of them, apart from a couple of shortcomings when dealing with serialization, which we’ll illustrate later in this article.

You might think that there is no real need to use the Persistent Connection API because Hubs is more powerful and expressive, but that’s not always true. For example, you might imagine a scenario where you want your clients to be in charge of dynamically deciding which Hubs to load among a set of available ones, and for that they might need to contact the server anyway to get some relevant information. Hubs are loaded all at once when calling start(), so you would not be able to use a Hub to do the first handshaking. But a persistent connection would be just perfect for the job, because that can be made available and used before starting any hub.

LEAVE A REPLY

Please enter your comment!
Please enter your name here