Chapter 4. Persistent connections

The lower-level API with which we can approach the persistent connection that SignalR offers is illustrated in Figure 4-1. This API provides us with a layer of abstraction that isolates us from the complexities inherent to keeping a permanent connection open between the client and the server and from the transports used to send information between both ends.

A diagram showing the abstraction levels used by SignalR to communicate between the client and the server. Starting at the bottom (at the lowest level of abstraction) are the Internet protocols. These support the transports used to maintain the virtual connection (WebSockets, Server-Sent Events, forever frame, and long polling). Above them are persistent connections and, at the highest level of abstraction, hubs.

Figure 4-1. Abstraction levels used by SignalR.

In practice, this API gives us access to the communication channel that is quite similar to the one used traditionally when working at a low abstraction level with sockets: On the server side, we can be notified when connections are opened and closed and when data are received, as well as sending information to clients. On the client side, we can open and close connections, as well as sending and receiving arbitrary data. Also, just like with sockets, messages have no format; that is, they are raw data—normally text strings—that we will have to know how to interpret correctly at both ends.

From the point of view of the client, its operation is very easy. We just have to initiate a connection to the server, and we will be able to use it to send data right away. We will perform the reception of information using a callback function that is invoked by the framework after its reception.

The server side is not very complex either. Take a look at Figure 4-2. Persistent connections are classes that inherit from PersistentConnection and override some of the methods that allow taking control when a relevant event occurs, such as the connection or disconnection of new clients or the reception of data. From any of them, we will be able to send information to the client that has caused the event, to all clients connected to the service, or to a group of them.

A diagram showing, on the left side, client code written in JavaScript, and on the right side, server code of a persistent connection. On the client side, a connection is created and initiated using the start() method, and the server receives the notification in the OnConnected() method. Later on, a text is sent from the client using send() and the server side receives said text in the OnReceived() method, from where it is forwarded to all connected clients using the Connection.Broadcast() method. The clients receive the message sent from the server in a callback function that they have previously set using the receive() method.

Figure 4-2. Conceptual implementation of persistent connections.

Now we will delve into all these aspects.

Implementation on the server side

For the clients to be able to connect to a SignalR service, first it is necessary to include this framework in our web project. For example, we can quickly do it by entering the following command in the NuGet package manager console:

PM> Install-package Microsoft.AspNet.Signalr

Each persistent connection is externally reachable via a URL, so, in a similar way to using other frameworks such as MVC or Web API, the next step could be to configure SignalR and associate each persistent connection to the path through which it will be available.

Mapping and configuring persistent connections

As usual, this is a process that must take place during application startup. In previous versions of SignalR, this registration was carried out in the global.asax, using extensions directly on the route collection of the application. However, since version 2.0, there is greater integration with OWIN[11], and this has changed the way it is implemented. In fact, from said version onwards, we need to register and configure SignalR in the middleware collection of the system in the startup process of the application.

We will come back to this, but for now it will suffice to know that the host process on which our application runs, based on OWIN, will search for a class called Startup in the root namespace of the application, and when it finds it, it will execute its Configuration() method. We will see that this convention can also be modified.

When the Configuration() method is executed, the execution environment will provide it with an argument in the form of an object implementing the IAppBuilder interface, which basically represents the application being initialized and contains a dictionary with configuration parameters and methods that allow us to configure the different OWIN middleware that will process the requests, such as SignalR, Web API, authentication, tracing, and so on.

Configuration is normally performed by using extension methods on IAppBuilder. These methods are provided by the frameworks themselves or by middleware to facilitate their implementation. In our case, we will use the MapSignalR() method, defined by SignalR as an extension method for IAppBuilder in the Owin namespace, to link the used persistent connections to the paths through which we access them, as shown in the following OWIN startup class:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.MapSignalR<EchoConnection>("/echo");
        // Configuration of other OWIN modules
    }
}

In any case, what we will achieve by calling MapSignalR() is “mapping” the paths of the type “/echo/something” to the class where we will implement the persistent connection, which in this case we have called EchoConnection. This path identifies the endpoint where the clients must be connected to consume the services. Obviously, at this point, there must be as many calls to the MapSignalR<TConnection>() method as the number of connections that are offered to the clients.

When this first step is completed, we are in position to implement the SignalR service, which will consist only in writing a class inheriting from PersistentConnection (defined in the Microsoft.AspNet.SignalR namespace):

public class EchoConnection: PersistentConnection
{
    // ...
}

This class will be instantiated by SignalR each time an HTTP connection is opened from a client to the server to process the request, which might depend on the transport selected each time. For example, if WebSockets is used as a transport, after the connection is established, the instance of PersistentConnection will remain active until the client disconnects, because it will be used both to send and receive data from the server. Contrariwise, if we use forever frame, an object will also be instantiated each time the client sends data, because those data are transmitted using a different request from the one used to obtain “push.”

Therefore, it is generally not a good idea to use instance members on this class to maintain system state because the instances are created and destroyed depending on the transport used to keep the connection open. For this, static members are normally used, although always appropriately protected from concurrent accesses that could corrupt their content or cause problems inherent to multithreaded systems. We also have to take into account that using the memory for storing shared data limits the scale-out capabilities of SignalR, because it will not be possible to distribute the load among several servers.

Events of a persistent connection

The PersistentConnection class offers virtual methods that are invoked when certain events occur that are related to the service and the connections associated with the class, such as the arrival of a new connection, the disconnection of a client, or the reception of data. To take control and enter logic where we want, it will suffice to override the relevant methods.

The most frequently used methods, which correspond to the events mentioned, are the following:

protected Task OnConnected(IRequest request, string connectionId)
protected Task OnDisconnected(IRequest request, string connectionId)
protected Task OnReceived(IRequest request, string connectionId,
                          string data)

First, note that all of them return a Task type object. This already gives us a clear idea of the extensive use of asynchrony capabilities present in the latest versions of the .NET platform and languages, inside which the goal is to always implement code that can be quickly and synchronously executed, or to return a background task represented by a Task object that performs it asynchronously.

We can also observe that there are always at least two parameters sent to the methods: an IRequest object and a text string called connectionId.

The first one, IRequest, allows accessing specific information on the request received, such as cookies, information about the authenticated user, parameters, server variables, and so on, as shown in Figure 4-3. The IRequest interface is a specific SignalR abstraction that allows separating the implementation of services from ASP.NET. As we pointed out when we mentioned OWIN, this will allow them to be hosted in any .NET application. We will look at this in more depth later on.

A screen shot showing the help provided by IntelliSense in Visual Studio, where we see the members of the IRequest interface: Cookies, Environment, Equals, GetHashCode(), the GetHttpContext() extender, GetType(), Headers, LocalPath, QueryString, ReadForm(), ToString(), Url, and User.

Figure 4-3. IntelliSense showing the members of IRequest.

The second parameter, connectionId, is a unique identifier associated with the connection that is generated by SignalR automatically during the initial negotiation process. The framework will use a GUID[12] by default, as shown in Figure 4-4.

A screen shot showing a SignalR application stopped at a breakpoint set in the OnConnected method, where we see that the value of the connectionId parameter that we are receiving is a GUID.

Figure 4-4. Value of a connectionID.

We can use the connection identifier to send direct messages to specific clients or to perform any type of personalized monitoring on them.

The following code shows the implementation of a simple service, which just counts the number of users connected to it and internally displays said number on the debug console. Note the use of thread-safe constructions to avoid problems associated with concurrent access from different execution threads to the static variable where the value is stored. These are precautions that we must take mandatorily when implementing this kind of service:

public class VisitorsCountConnection: PersistentConnection
{
    private static int connections = 0;
    protected override Task OnConnected(IRequest request,
                                        string connectionId)
    {
        Interlocked.Increment(ref connections);
        Debug.WriteLine("Visitors: " + connections);
        return base.OnConnected(request, connectionId);
    }
    protected override Task OnDisconnected(IRequest request,
                                           string connectionId)
    {
        Interlocked.Decrement(ref connections);
        Debug.WriteLine("Visitors: " + connections);
        return base.OnDisconnected(request, connectionId);
    }
}

Other less utilized methods also exist in the PersistentConnection class, such as OnReconnected() and OnRejoiningGroups(). The former can be useful to take control when there is a reconnection—that is, when the client has connected to the server again after the physical connection of the transport has closed due to a time-out, a communication problem between the two ends, an application crash, a server reboot, or any other such incident. From the point of view of the SignalR connection, it is still the same client and has the same identifier, thus the invocation of OnReconnected() instead of treating it as a new connection. The OnRejoiningGroups() method allows taking control when a connection is reopened after a time-out and determining to which groups the connection should be reassigned.

The OnReceived() method of the persistent connection allows processing the data sent by the clients. In this method, the information submitted will be received as a text string:

protected override Task OnReceived(IRequest request,
                                   string connectionId,
                                   string data)
{
    // Do something interesting here
}

Of course, if an object serialized in any format came in this string, we should deserialize it manually before processing it. If it was JSON serialized, which will likely be the usual case, we could use the Json.NET library, which will be available in our project because it is required by SignalR:

using Newtonsoft.Json;
// ...
protected override Task OnReceived(IRequest request,
                                   string connectionId,
                                   string data)
{
    var message = JsonConvert.DeserializeObject<ChatMessage>(data);
    if (message.MessageType == MessageType.Private)
    {
        var text = message.Text;
        // ...
    }
    // ...
}

Sending messages to clients

There are tools available to the classes that inherit from PersistentConnection, which allow sending information to all connected clients, to specific clients identified by their connectionId, or to groups of clients.

To send a message asynchronously to all clients connected to the service, we will use the Connection property to invoke the Broadcast() method as follows:

protected override Task OnConnected(IRequest request,
                                    string connectionId)
{
    // Notify all connected clients
    return this.Connection.Broadcast(
                   "New connection: " + connectionId);
}
protected override Task OnDisconnect(IRequest request,
                                     string connectionId)
{
    // Notify all connected clients
    return this.Connection.Broadcast("Bye bye, " + connectionId);
}

In this example, each time a new client connects to the service, the notification text is sent to all connected clients (including the newcomer) through the SignalR connection. And, in the same way, we make use of the OnDisconnected() method, by which we are informed of the disconnection of a client, to notify the rest of the users.

The parameter that we pass to Broadcast() is an object type (as shown in Figure 4-5), which means that we can send any type of object, which SignalR will serialize to JSON automatically.

A screen shot showing context-sensitive help in Visual Studio during coding. It shows that the value parameter of the Broadcast() method is an object type.

Figure 4-5. BroadCast() receives an object type parameter.

The Broadcast() method also accepts an optional parameter where we can specify a collection of connectionIds to which the message will not be sent. The following example shows how to use this feature to send a notification to all users of the service except the one who has just connected:

protected override Task OnConnected(IRequest request,
                                    string connectionId)
{
    return this.Connection.Broadcast(
             "A new user is online! Let's give them a warm welcome!",
             connectionId  // Do not notify the current user
    );
}

The list of identifiers excluded from the return is given in a parameter of the params string[] type, so they can be specified directly as parameters separated by commas or as an array of text strings:

protected override Task OnConnected(IRequest request,
                                    string connectionId)
{
    return this.Connection.Broadcast(
             "A new user is online! Let's give them a warm welcome!",
             new[] { connectionId }
    );
}

To send messages to a specific client, we need to know its connectionId. Normally, this is not a problem, because this information will be available in the methods from which we will use these calls. The following example displays a welcome message to the client initiating a connection only:

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

A very interesting aspect that we already anticipated is the fact that, both in the Send() method and in Broadcast(), the message to be sent to the clients is an object type. This means that messages are not limited to text; it is entirely possible to send any type of object, which will be automatically serialized in JSON format before being returned to the client. This allows sending messages with a structure beyond the mere character string:

protected override Task OnConnected(IRequest request,
                                    string connectionId)
{
    var message = new {
                          type = MessageType.NewUser,
                          id = connectionId
                      };
    return this.Connection.Broadcast(message);
}

Note

If the object supplied to Send() or Broadcast() is of the ArraySegment<byte> type, it will not be serialized. This could be useful if we already have the JSON representation of the object to be sent, because it would prevent double serialization of the object.

Although the most frequent procedure for making deliveries will be using the methods just described, the Send() method has some additional overloads. One of them receives as arguments a list of strings representing the connection identifiers to which we want to send the message. Another, actually used internally by Broadcast() to make the delivery, simply receives a ConnectionMessage type object that defines the recipients, the message, and, optionally, the connection identifiers that we want to exclude:

var connMessage = new ConnectionMessage(
                               Connection.DefaultSignal, textMessage);
return Connection.Send(connMessage);

The preceding code would be equivalent to making a broadcast with the value of textMessage. In fact, the Broadcast() method uses Send() internally in a way that is quite similar to the one shown. Connection.DefaultSignal is a unique code made up by the full name of the persistent connection class preceded by a constant prefix, which in this case indicates that the message must be sent to all the users “subscribed” to this signal, which are all those connected to the persistent connection.

Asynchronous event processing

As you can imagine, calls to the Send() or Broadcast() methods that we have already used in some of our examples could take too long to execute if communications between both ends are very slow, or if the number of connections is very large. If they were executed synchronously, the threads in charge of performing these tasks would be blocked until said tasks ended. In high load environments, this is truly a waste of resources; we want these threads to be released as soon as possible so that they can keep managing requests and providing their services.

For this reason, those commands are executed asynchronously, returning a Task object representing the task that will take care of them in the background. Consequently, we can return the result of the call from the body of the method, as we have been doing with the code shown up until now:

return this.Connection.Broadcast(message);

Following this same example, we must use asynchrony inside the methods of the persistent connection whenever we are to perform long tasks, and especially those requiring the use of external elements such as the access to web services or external APIs, or heavy queries to databases.

The following example shows how to use the async/await construct of C# 5 to invoke asynchronous methods in a very clean way:

protected override async Task OnConnected(IRequest request,
                                          string connectionId)
{
    // Store the new connection in the database
    await _services.SaveNewConnectionAsync(connectionId);
    // And then, send the notifications
    await this.Connection.Broadcast("A new user is online!");
    await this.Connection.Send(connectionId,
                               "Welcome, " + connectionId);
}

Connection groups

We have seen how SignalR allows sending messages to individual clients, identified by their connectionId, or to all clients connected to a service. Although these capabilities cover multiple scenarios, it would still be difficult to undertake certain tasks that require selective communication with a specific group of connections.

Imagine a chat service with different rooms. When a user enters a specific room and writes a text, ideally it would be sent only to the users present in said room. However, with the functionalities studied up to this point, implementing this very simple feature would not be easy.

For this reason, SignalR offers the possibility of grouping connections based on whatever criteria we deem relevant. For example, in a chat, we might create a group for each room; in an online game, we could group the users competing in the same match; in a multiuser document editor similar to Google Docs, we could create a group for every document being edited.

To manage those groups, we use the Groups property, available in the PersistentConnection class and thus in all its descendants. This property is of the IConnectionGroupManager type, and, among other things, it provides methods to add a connection identified by its connectionId to a group and, likewise, remove it.

The following example shows how we might interpret the commands join <groupname> and leave <groupname> coming from a client, to respectively add the connection to the group specified and remove it from it:

protected override Task OnReceived(IRequest request,
                                   string connectionId,
                                   string data)
{
    var args = data.Split(new[] {" "},
                             StringSplitOptions.RemoveEmptyEntries);
    if(args.Length == 2 && args[0].ToLower()=="join")
    {
        return this.Groups.Add(connectionId, args[1]);
    }
    if (args.Length == 2 && args[0].ToLower() == "leave")
    {
        return this.Groups.Remove(connectionId, args[1]);
    }
    // ...
}

The groups do not need to exist previously nor do they require any kind of additional managing. They are simply created when the first connection is added to them and are automatically eliminated when they become empty. And, of course, one connection can be included in as many groups as necessary.

To send information through the connections included in a group, we can use the Send() method as follows:

protected override Task OnReceived(IRequest request,
                                   string connectionId,
                                   string data)
{
    int i;
    if ((i = data.IndexOf(":")) > -1)
    {
        var groupName = data.Substring(0, i);
        var message = data.Substring(i + 1);
        return this.Groups.Send(groupName, message);
    }
    // ...
}

As you can see, the preceding code would interpret a message such as “signalr:hello!” by sending the text “hello!” to all clients connected to the “signalr” group.

Also, there is an overload of the Send() method that allows specifying a list of group names as the recipient:

var groupNames = new[] { "firstgroup", "secondgroup"};
this.Groups.Send(groupNames, message);

Unfortunately, for reasons related to the structural scalability of SignalR, we cannot obtain information about the groups, such as the list of connections included in them, not even how many there are. Neither can we know, a priori, what groups are active at a given moment. If we want to include these aspects in our applications, we must implement them ourselves, outside SignalR.

The OWIN startup class

We have previously seen where to enter the mapping and configuration code of our persistent connections and, in general, of any middleware based on OWIN. We will now go back to that briefly, to go over some details that were left pending.

When an OWIN-based system starts up, the host process on which it is executed will try to execute configuration code that must be implemented in a member predefined by convention. By default, it will try to execute the Configuration() method of the Startup class, which must be located in the root namespace of the application. However, to adapt it to our preferences, it is possible to modify this convention in one of the following ways:

  • Specifying the class and the method that we want to employ by using the assembly attribute OwinStartup:

    [assembly:OwinStartup(typeof(MyApp.MyStartupClass),
                          methodName: "MyConfigMethod")]
  • Including the entry “owin:AppStartup” in the <AppSettings> section of the .config file of the application and setting as a value the fully qualified name of the class and the method to be used:

    <configuration>
        <appSettings>
            ...
           <add key="owin:AppStartup"
                value="MyApp.MyStartupClass.MyConfigMethod"/>
        </appSettings>
        ...
    </configuration>

In either case, the configuration method can be static or an instance method (although in the latter case the class must have a public constructor without parameters), and it must necessarily be defined with an IAppBuilder parameter:

namespace MyApp
{
    public class MyStartupClass
    {
        public void MyConfigMethod(IAppBuilder app)
        {
            // Configure OWIN app here
        }
    }
}

Because all the OWIN middleware of the application will be initialized in this method, it might be advisable to take the specific configuration of SignalR to an independent class. Obviously, if we are using the conventions of ASP.NET MVC or Web API on file location, it will probably be a much better idea to enter it into the /App_Start directory and, if the configuration code is too long, we should even separate it into an independent class and file. The following example shows a possible way to organize these files:

//
// File: /App_Start/Startup.cs
//
public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        SignalRConfig.Setup(app);
    }
}
//
// File: /App_Start/SignalRConfig.cs
//
public class SignalRConfig
{
    public static void Setup(IAppBuilder app)
    {
        app.MapSignalR<EchoConnection>("/echo");
    }
}

In any case, this does not constitute a norm to be used mandatorily. The point is to stick to the conventions defined and well-known by the development team so that things are located where it is expected they should be.

Implementation on the client side

SignalR offers many client libraries with the purpose that practically any kind of application can use the connection provided by the framework. Although later on we will see examples of implementations of other types of clients, for the moment we will deal with creating clients from the web using JavaScript, mainly because it is easy and widely done.

In any case, the concepts and operating philosophy that we are going to see are common to all clients and project types.

Initiating the connection by using the JavaScript client

An important aspect to underline is that this client is completely and solely based on JavaScript, so it can be used in any kind of Web project: Web Forms, ASP.NET MVC, Web Pages, PHP, or even from pure HTML pages.

In fact, to access services in real time from an HTML page, it is enough to reference jQuery (version 1.6.4 or above)—because the client is implemented as a plug-in of this renowned framework—and then the jquery.signalR library (version 2.0.0 or above). We can find both of them in the /Scripts folder when we install the Microsoft.AspNet.SignalR.JS package from NuGet, or the complete Microsoft.AspNet.SignalR package, which includes both the client and server components for web systems.

<script src="Scripts/jquery-1.6.4.min.js"></script>
<script src="Scripts/jquery.signalR-2.0.0.min.js"></script>

After they are referenced, we can begin to work with SignalR from the client side. The first thing we have to do is open a connection to the server. For this, it is necessary for the client to know the URL by which the persistent connection is accessible. As we said before, it is assigned during application startup. Here you can see an example:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.MapSignalR<EchoConnection>("/realtime/echo");
    }
}

Given the previous configuration, which maps the path /realtime/echo to the persistent connection implemented in the EchoConnection class, the following code shows how to create and open a connection to it using JavaScript:

<script type="text/javascript">
    $(function() {
        var connection = $.connection("/realtime/echo");
        connection.start();
        // ...
    });
</script>

As you can see, this call is being entered in the page initialization, following the customary pattern used to develop with jQuery. Although it does not necessarily have to be this way, this technique ensures that the code will be executed when the page has loaded completely and the DOM is ready to be operated on.

The call to the start() method is asynchronous, so the execution of the script will continue on the next line even if the connection has not been opened yet. This particular detail is very important, because we will not be able to use the connection until it has been established. If this does not happen, an exception will be thrown with a very clear message:

A selection of JavaScript code showing an invocation of the send() method just after the call to start(), without waiting for the connection to be established. This causes an error that states “Connection must be started before data can be sent. Call .start() before .send()”.

Fortunately, this method has overloads that allow us to specify a callback function that will be executed when the connection is open and the process of transport negotiation with the server has been completed:

var connection = $.connection("/realtime/echo");
connection.start(function() {
    // Connection established!
    connection.send("Hi there!");
});

It is important to know that the specified callback function will be executed whenever the connection is initiated. That is, if the start() method is invoked from another point in the client code, the previously registered callback function will also be executed when the connection process completes successfully.

It is also possible to use the well-known promise[13] pattern and the implementation of jQuery based on Deferred[14] objects to take control when the connection has been successful, as well as in the event that an error has occurred. This is the recommended option:

var connection = $.connection("/realtime/echo");
connection.start()
    .done(function() {
        connection.send("Hi there!"); // Notify other clients
    })
    .fail(function() {
        alert("Error connecting to realtime service");
    });

After the connection is established, we can begin to send and receive information by using the mechanisms that are described later in this chapter.

From this point onwards, we can also close the connection explicitly, by invoking the connection.stop() method, or obtain the identifier of the current connection, generated by the server during the negotiation phase, through the connection.id property.

Support for older browsers

A problem that might arise when we open the connection using the start() method is that the user’s browser might be too old and not have the JSON parser built in—for example, as it happens with Internet Explorer 7. In this case, execution will stop, indicating the problem and even its solution:

A selection of JavaScript code showing that when attempting to open a connection with an old browser that does not have a JSON parser, an error is generated with the text “No JSON parser found. Please ensure json2.js is referenced before the SignalR.js file if you need to support clients without native JSON parsing support?for example, Internet Explorer<8”.

The json2.js file that we are told must be referenced can be easily obtained from NuGet with the following command in the console:

PM> Install-package json2

When this is done, we would have to include only the reference to the script before doing so with SignalR:

<script src="Scripts/jquery-1.6.4.min.js"></script>
<script src="Scripts/json2.min.js"></script>
<script src="Scripts/jquery.signalR-2.0.0.min.js"></script>

Support for cross-domain connections

SignalR includes “out-of-the-box” support for connections with a different server from the one that has served the script currently being executed, something that is normally not allowed for security reasons. This type of request, called cross-domain, requires the use of some kind of special technique to avoid this restriction that is usual in browsers. Examples of those techniques are JSONP[15] or, only in some browsers, the use of the CORS[16] specification.

JSONP is not particularly recommended for security reasons, but if our service must provide support to clients using older browsers such as Internet Explorer 7, it might be the only option available to obtain connections from external domains. This feature is disabled by default at server level, but it can be activated by supplying a ConnectionConfiguration object during initial mapping, setting its EnableJSONP property to true:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var config = new ConnectionConfiguration()
                     {
                         EnableJSONP = true
                     };
        app.MapSignalR<EchoConnection>("/realtime/echo", config);
    }
}

This way, the server will be ready to accept connections using long polling transport with JSONP.

Because CORS is quite a cross-cutting technique and independent of frameworks and applications, it is implemented as OWIN middleware; therefore, to allow this type of connection, it will be necessary to first download the module using NuGet:

PM> Install-Package microsoft.owin.cors

After this, we can specify that we want to use CORS in our initialization method, entering the module into the OWIN pipeline with the extension method UseCors() so that it is executed before the SignalR middleware:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.Map("/realtime/echo",
            map => {
                       map.UseCors(CorsOptions.AllowAll);
                       map.RunSignalR<EchoConnection>();
                   }
        );
    }
}

Note that in this case we have used a different construct to map and configure the service available in the path /realtime/echo. First, we have used the Map() extension to specify the path just once, followed by a lambda that receives as a parameter the mapping that we are defining (in turn, an IAppBuilder object). On this parameter, we have used the extensions provided by the different modules to enter them into the pipeline associated to the URL provided:

  • UseCors() enables the use of CORS at the server, according to the options sent as an argument in the form of a CorsOptions object. CorsOptions.AllowAll is an object preconfigured in a very permissive mode; it allows all origins, verbs, and headers. However, it is possible to supply it a customized CorsOptions object to fine-tune its configuration and usage policies.

  • RunSignalR() is the equivalent of the MapSignalR() method that we had been using up until now, but as opposed to it, we do not need to supply it the path because it has already been defined.

At the client side, SignalR will automatically detect that we are making a cross-domain connection if when the connection is made it notices that an endpoint has been specified that is hosted in a domain different than the one of the current page. In this case, it will try to use CORS to make the connection. Only if it is not possible will it automatically fall back to JSONP, using long polling as the transport.

In scenarios where we need to force this last option, we can specifically indicate that we want to use JSONP when initiating the connection from the client. At this moment, it is possible to send an object with settings that allow fine-tuning said process:

var connection = $.connection("http://localhost:3701/realtime/echo");
connection.start({ jsonp: true })
          .done(function() {
              alert(connection.transport.name);
          });

The connection.transport property contains the transport used by the current connection.

Sending messages

As you can probably guess, to send information to the server from the JavaScript client, we will use the send() method available in the connection object, which represents the connection created before.

This method accepts the object to be sent as a parameter. It will be received in the data parameter of the OnReceived() method of the server in the form of a text string, as we saw previously:

A selection of client code in which the send() method is used to send a text to the server. Following is the server implementation of the persistent connection, where the OnReceived() method receives the text that was sent by the client in its data parameter.

Of course, we can send any object type. SignalR will serialize it automatically before sending it:

$("#buttonSend").click(function () {
    var obj = {
        messageType: 1, // Broadcast message, type = 1
        text:        $("#text").val(),
        from:        $("#currentUser").val(),
    };
    connection.send(obj);
});

Because what would arrive at the server would be a text string with the JSON representation of the object, to manipulate the data comfortably it would be necessary to deserialize the string and turn it into a CLR object, as we have already seen:

// Server code
protected override Task OnReceived(IRequest request,
                                   string connectionId,
                                   string data)
{
    var message = JsonConvert.DeserializeObject<ChatMessage>(data);
    if (message.MessageType == MessageType.Broadcast)
    {
        return this.Connection.Broadcast(
                   "Message from "+message.From +
                   ": " + message.Text);
    }
    // ...
}

Although the send() method is expected to adhere to the promise pattern in the future, this is currently not so, and there is no direct way of knowing when the transfer process has ended or whether there has been an error in its execution.

Nevertheless, it is possible to detect errors on the connection by using the error() method of the connection to set the callback function to be executed when there is any problem on it:

var connection = $.connection("/chat");
connection.error(function (err) {
    alert("Oops! It seems there is a problem. 
" +
                "Error: " + err.message);
});
connection.start();

The callback function receives a JavaScript object from which we can obtain information describing the problem that has occurred, as shown in Figure 4-6.

A screen shot showing a browser executing a SignalR application in which there has been a connection problem. A warning appears with the text “Oops! It seems there is a problem. Error: WebSocket closed”.

Figure 4-6. Connection interrupted captured by the callback.

Receiving messages

The reception of data sent from the server is performed at the JavaScript client by registering the callback that will be executed each time information is received. This registration is performed by calling the received() method of the object that represents the connection with the server and supplying it the function for data handling:

connection.received(function (msg) {
    $("#contents").append("<li>" + msg + "</li>");
});

As you can see, as a parameter, this function receives the data sent from the server in the form of a directly usable object. SignalR will be responsible for serializing and deserializing the data on both ends.

Thus, if a character string has been sent from the server, we can obtain it and process it directly at the client, as in the preceding example. Conversely, if structured data are sent from the server, they are automatically serialized to JSON, and in the input parameter of our callback function we will directly receive the JavaScript object ready for use:

// ======================================================
// Server code
protected override Task OnConnected(IRequest request,
                                    string connectionId)
{
    var message = new
    {
        type = MessageType.NewUser,
        id = connectionId,
        text = "New user!"
    };
    return this.Connection.Broadcast(message);
}
// ======================================================
// Client code (Javascript)
connection.received(function (msg) {
    $("#contents").append(msg.text + ". Id: " + msg.id);
});

Sending additional information to the server

We have seen that the events available at the server receive an IRequest type parameter through which it is possible to access the environment of the request that is behind the persistent connection. Thus, using this object, it would be possible to retrieve, among other things, the identity of the user authenticated into the system, information sent in cookies, or even the parameters of the query string or server headers.

For example, if you are using cookie-based authentication, when a user is authenticated in a website, the browser will automatically include the authentication cookie in all requests to SignalR, which offers the possibility of implementing code such as the following at the server:

protected override Task OnConnected(IRequest request,
                                    string connectionId)
{
    string message = request.User.Identity.IsAuthenticated
                         ? "Welcome, " + request.User.Identity.Name
                         : "You must be logged in!";
    return Connection.Send(connectionId, message);
}

In the same vein, when it is necessary to send additional information from the client to the server, we can make use of cookies. They are easy to manage, and they allow entering arbitrary information into requests that are going to be made a posteriori, as illustrated in the following example:

// Client side:
var username = prompt("Your username");
document.cookie = "username=" + username;
var connection = $.connection("/realtime/chat");
...
connection.start();
// Server side:
protected override Task OnConnected(IRequest request,
                                    string connectionId)
{
    Cookie cookie;
    var username =
        request.Cookies.TryGetValue("Username", out cookie)
        ? cookie.Value
        : connectionId;
    var message = "Welcome, " + username + "!";
    return Connection.Send(connectionId, message);
}

Another possibility would be to use the query string to send information. For this, the SignalR client allows us to specify an additional parameter at the point at which the connection is defined. In this parameter, we can add key-value mappings, either in the form of a string or an object, which will be annexed to all the requests made to the SignalR server:

// Client side:
var name = prompt("Your username");
var conn = $.connection(
               "/realtime/chat",
               "username="+name // or { username: name  }
);
...
conn.start();
// Server side:
protected override Task OnConnected(IRequest request,
                                    string connectionId)
{
    var userName = request.QueryString["username"] ?? connectionId;
    var message = "Welcome, " + userName + "!";
    return Connection.Send(connectionId, message);
}

Other events available at the client

The connection object has a large number of events that allow us to take control at certain moments in the life cycle of the connection if we register the callback methods that we want to be invoked when the time comes. The most interesting ones are the following:

  • received() and error(), which we already saw and which allow us to specify the function to be executed when data are received or when an error occurs in the connection.

  • connectionSlow(), which allows entering logic when the connection is detected to be slow or unstable.

  • stateChanged(), invoked when the state of the connection changes.

  • reconnected(), when there is a reconnection of the client after its connection has been closed due to time-out or any other cause.

In the SignalR repository in GitHub[17], you can find the documentation about methods, events, and properties offered by the JavaScript client connections.

Transport negotiation

We have seen that, after the reference to the connection is obtained, the start() method really initiates communication with the server, thus beginning the negotiation phase in which the technologies or techniques to be used to maintain the persistent connection will be selected.

First, there will be an attempt to establish the connection using WebSockets, which is the only transport that really supports full-duplex on the same channel and is therefore the most efficient one. If this is not possible, to determine which is the most efficient solution available at both ends, a fallback procedure will begin:

  • In the browsers that support it (basically all with the exception of Internet Explorer), contact will be attempted using Server-Sent Events, which at least provides a standard mechanism to obtain push.

  • In Internet Explorer, the possibility of using forever frame will be examined.

If neither of the previous steps has succeeded, to maintain the persistent connection, long polling will be tried.

It is easy to trace this process, because the SignalR web client allows us to activate the tracing of events with the JavaScript console available in major browsers. (See Figure 4-7.) This option is enabled at the moment the connection is created, setting the third parameter of the $.connection() method to true:

var connection = $.connection("/realtime/chat", null, true);

Or, in an equivalent and much more legible way, directly on the logging property of the connection:

var connection = $.connection("/myconn");
connection.logging = true;
A screen shot showing the console provided by the developer tools of Internet Explorer 11, where we see the log of the process to connect to a SignalR service. There are several events recorded: start of the negotiation, connection to the endpoint, successful opening of the WebSocket, and beginning of the connection monitoring after it is opened.

Figure 4-7. Trace of the SignalR client in Internet Explorer 11.

We can also trace requests using Fiddler[18] or the development tools available in our browsers to trace requests, and thus we can view the main terms of the “agreement” reached by the client and the server.

Figure 4-8 shows the tracing of connections using the development tools of Chrome, where you can see the process of content negotiation between this browser and IIS 8. Both support WebSockets.

A screen shot showing the Network tab of the developer tools of Chrome, where we see the different requests generated during a transport negotiation process, which results in a WebSockets connection: a first request to /negotiate to start negotiation and another to /connect to initiate the connection.

Figure 4-8. Process of transport negotiation with Chrome.

However, Fiddler[19] is used in Figure 4-9 to show the negotiation procedure of Internet Explorer 7 and how the fallback mechanism works to select the best transport supported by it—forever frame:

A screen shot showing the requests recorded by Fiddler during a transport negotiation of a connection made from Internet Explorer 7. There is a first connection to /negotiate to initiate negotiation, and another one to /connect in which a parameter is sent specifying that the transport is to be forever frame.

Figure 4-9. Transport negotiation procedure with Internet Explorer 7.

Finally, Figure 4-10 shows the trace by the Firefox console (Firebug) of the negotiation of a cross-domain connection, which is resolved employing the WebSocket transport using CORS.

A screen shot showing the Firebug console on Firefox in a cross-domain negotiation. In the first entry in the trace, it states that SignalR has detected that it is a cross-domain connection, and following are the connection?s own events: start of negotiation, initiation of the connection, opening of the WebSocket, and beginning of connection monitoring.

Figure 4-10. Cross-domain connection negotiation with Firefox.

Adjusting SignalR configuration parameters

SignalR allows us to adjust certain parameters that affect the way in which connections are made, as well as other aspects related to their management. This is done through a configuration object available in the global object GlobalHost. As we shall see throughout this book, this object allows static access to some interesting functions of SignalR, but for now, we will focus on its Configuration property, which is where we will be able to adjust the value of the following parameters:

  • TransportConnectTimeout, which is a TimeSpan that specifies the length of time that a client is to allow for the connection to be made using a transport, before falling back to another with inferior features or before the connection fails. The default value is five seconds.

  • ConnectionTimeout is a TimeSpan specifying the length of time for which a connection must remain open and inactive before a time-out occurs. The default value is 110 seconds. It is effective only for transports that don’t support keep alive, or if keep alive is disabled.

  • DisconnectTimeout specifies the length of time from when a connection is closed until the disconnect event is fired. The default value is 30 seconds.

  • KeepAlive is a nullable TimeSpan that allows us to specify the length of time between messages sent to the server indicating that the client remains active. The default value is 10 seconds, but we can disable it by setting a null value; see Figure 4-11. In any case, we cannot enter a value smaller than two seconds nor greater than one third of the value of DisconnectTimeout.

    A screen shot of an exception caught by Visual Studio at runtime when setting a value for the KeepAlive parameter that is less than three times the KeepAlive value. The message shown is “KeepAlive value must be no more than a third of the DisconnectTimeout”.

    Figure 4-11. Exception during application startup.

  • DefaultMessageBufferSize is an integer indicating the size of the message buffer for a specific signal (connection, group, users, and so on). The default value is 1,000.

  • LongPollDelay is a TimeSpan that allows us to specify the length of time that a client must wait before opening a new long polling connection after having sent data to the server. The default value is 0.

Some of these parameters, such as TransportConnectTimeout or KeepAliveTimeout, are sent to the client in the negotiation phase of the connection so that it can apply them during said connection.

Naturally, for them to take effect, the changes on the configuration properties should be made during application startup—for example, like this:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        GlobalHost.Configuration
                  .DisconnectTimeout = TimeSpan.FromSeconds(30);
        app.MapSignalR<EchoConnection>("/echo");
    }
}

Note

These settings are also valid when we use hubs, because they are global configurations of the server.

Complete example: Tracking visitors

We will now look at the code of a complete example, both on the client and server sides, with the purpose of consolidating some of the concepts addressed throughout this chapter.

Specifically, we will track the mouse of the visitors of a page and send this information to the rest of the users in real time. Thus, every visitor will be able to see the position of other users’ cursors on their own screen and follow their movement across it.

Figure 4-12 shows the system being executed on a busy page.

A screen shot showing the application “Visitor tracking” running. There is a browser displaying a page on which we can see the real-time position of the mouse of all users who are visiting it. Each connected user is represented by their connectionId.

Figure 4-12. System for tracking users in real time in operation.

Project creation and setup

For the purpose of creating the application that we will develop over the following pages, it is necessary to first create a project of the “ASP.NET Web Application” type from Visual Studio 2013 and then select the “Empty” template to create a completely empty project[20]. The version of the .NET framework used must be at least 4.5.

After we have created it, we must install the following package using NuGet:

PM> install-package Microsoft.AspNet.SignalR

Implementation on the client side

HTML markup (tracking.html)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="Scripts/jquery-1.6.4.min.js"></script>
    <script src="Scripts/jquery.signalR-2.0.0.min.js"></script>
    <script src="Scripts/tracking.js"></script>
    <style>
        .client {
            position: absolute;
            background-color: white;
            -moz-box-shadow: 10px 10px 5px #888;
            -webkit-box-shadow: 10px 10px 5px #888;
            box-shadow: 3px 3px 3px #888;
            border: 1px solid #a0a0a0;
            padding: 3px;
        }
    </style>
</head>
<body>
    <h1>Lorem ipsum</h1>
    <p>Lorem ipsum dolor sit amet, […]</p>
    <p>Integer elit augue, […] </p>
</body>
</html>

Scripts (Scripts/Tracking.js)

$(function() {
  /* SignalR client */
  var connection = $.connection("/tracker");
  connection.start(function () {
      startTracking();
  });
  connection.received(function (data) {
      data = JSON.parse(data);
      var domElementId = "id" + data.id;
      var elem = createElementIfNotExists(domElementId);
      $(elem).css({ left: data.x, top: data.y }).text(data.id);
  });
  function startTracking() {
      $("body").mousemove(function (e) {
          var data = { x: e.pageX, y: e.pageY, id: connection.id };
          connection.send(data);
      });
  }
  /* Helper functions */
  function createElementIfNotExists(id) {
      var element = $("#" + id);
      if (element.length == 0) {
          element = $("<span class='client' " +
                      "id='" + id +"'></span>");
          var color = getRandomColor();
          element.css({ backgroundColor: getRgb(color),
                        color: getInverseRgb(color) });
          $("body").append(element).show();
      }
      return element;
  }
  function getRgb(rgb) {
      return "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
  }
  function getInverseRgb(rgb) {
      return "rgb(" + (255 - rgb.r) + "," +
                      (255 - rgb.g) + "," + (255 - rgb.b) + ")";
  }
  function getRandomColor() {
      return {
          r: Math.round(Math.random() * 256),
          g: Math.round(Math.random() * 256),
          b: Math.round(Math.random() * 256),
      };
  }
});

Implementation on the server side

Persistent connection (TrackerConnection.cs)

using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
public class TrackerConnection : PersistentConnection
{
    protected override Task OnReceived(IRequest request,
                                       string connectionId,
                                       string data)
    {
        return Connection.Broadcast(data);
    }
}

Startup code (Startup.cs)

using Owin;
public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.MapSignalR<TrackerConnection>("/tracker");
    }
}


[11] OWIN (Open Web Interface for .NET): http://owin.org/

[14] The Deferred object in jQuery: http://api.jquery.com/category/deferred-object/

[15] JSONP (JSON with Padding): http://en.wikipedia.org/wiki/JSONP

[16] CORS (Cross-Origin Resource Sharing): http://en.wikipedia.org/wiki/Cross-origin_resource_sharing

[20] In Visual Studio 2012, we can achieve the same goal by creating a project from the template “ASP.NET Empty Web Application.”

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

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