Inside SignalR – Addressing Clients, Return Values, Broken Connections and Security

Introduction

During my last lecture about ASP.NET SignalR in the .Net Developer Group Berlin-Brandenburg, some questions came up from the audience. I like to try to answer these questions with this blog post. The questions were about how address specific clients from the server side, how to return values after calling a function at a specific client and how broken connections are handled. And also the evergreen-question came up: How to secure SignalR applications. This post is about SignalR 2 and will not cover previous versions. You can find very good tutorials here: ASP.NET SignalR 2. Let’s have a look at the questions step by step.

Addressing Clients

Your specific hub class at the server is derived from the base class Hub. This base class provides the property Clients that gives you a reference to an object that implements the interface IHubCallerConnectionContext<dynamic>. This property allows you to access the clients: you can access for example the calling client withClients.Caller, where Caller is a dynamic object that represents the calling client. Or you can access all clients that are connected by using Clients.All, what is a dynamic object, too.

But the more interesting question is, how to access specific clients. Each client has a connection id that is generated when the client connects to the server hub. The id can be obtained via the connection context. Consider the following code:

public class PlanningGridHub : Hub
{
    public override Task OnConnected()
    {
        Console.WriteLine("[{0}] Client '{1}' connected.", DateTime.Now.ToString("dd-mm-yyyy hh:MM:ss"), Context.ConnectionId);
 
        return base.OnConnected();
    }
}

As you can see in bold, you can access a property called **Context *of type HubCallerContext. *The **Context **property comes from the class **HubBase **that is the base class of the **Hub **class from that your specific hub is derived. As you can see below, the context gives you access to communication-related information such as cookies, the request from the client, the current user AND: the connection id. This connection id explicitly identifies the incoming client. Each client has its own unique id.

public class HubCallerContext
{
    // Summary:
    //     This constructor is only intended to enable mocking of the class. Use of
    //     this constructor for other purposes may result in unexpected behavior.
    protected HubCallerContext();
    public HubCallerContext(IRequest request, string connectionId);
 
    // Summary:
    //     Gets the connection id of the calling client.
    public virtual string ConnectionId { get; }
    
    // Summary:
    //     Gets the headers for the request.
    public virtual INameValueCollection Headers { get; }
    
    // Summary:
    //     Gets the querystring for the request.
    public virtual INameValueCollection QueryString { get; }
    
    // Summary:
    //     Gets the Microsoft.AspNet.SignalR.IRequest for the current HTTP request.
    public virtual IRequest Request { get; }
    
    // Summary:
    //     Gets the cookies for the request.
    public virtual IDictionary<string, Cookie> RequestCookies { get; }
    
    // Summary:
    //     Gets the System.Security.Principal.IPrincipal for the request.
    public virtual IPrincipal User { get; }
}

You can use this connection id e.g. in the override **OnConnected **in your hub to store the clients in your own collection or something like that. You can then access a specific client with a call to **Clients.Client(connectionId) **with the appropriate connection id as parameter. The server will try to call only this specific client given by the id.

One point more: instead of managing all clients for your own, you can use the Groupsproperty in your hub class. This property is provided by the HubBase class and lets you manage clients in self-defined groups. You can then access specific groups by calls toClients.Groups() for addressing more then one group or **Clients.Group() **to access a specific groups. Both method work with the names of the groups to address them.

Return Values

On question during my lecture about SignalR was, how return values can be achieved. Here we have to look at two cases: first, if the client calls a server method and second, if the server calls client methods.

Client calling Server Method

To call a server method that returns a value, you can use an overload of the Invokemethod for .NET clients. Please see the following definition of this method. This method takes a generic parameter that defines the type of the return value. As you can see in the method definition, this method returns a Task of T. So this method invokes the server method asynchronously and when the server method finished, it returns the return value that is of type T.

// Summary:
//     Executes a method on the server side hub asynchronously.
//
// Parameters:
//   method:
//     The name of the method.
//
//   args:
//     The arguments
//
// Type parameters:
//   T:
//     The type of result returned from the hub.
//
// Returns:
//     A task that represents when invocation returned.
Task<T> Invoke<T>(string method, params object[] args);

For the example I used in the lecture, I simply return a boolean values that indicates whether the operation was successful or not:

bool success = await _proxy.Invoke<bool>("SaveAccountData", accountId, values);

For JavaScript clients you can define a function that is called when the server method returns:

proxy.server.saveAccountData().done(function (success) 
{
    alert(success);
});

Server calling Client Method

First I want to say: it is not possible to get return values from methods called at the client. One can argument, that a strong-typed hub can be used where the used interface defines some methods that return values. Consider the code below.

public interface IPlanningGridClient
{
    bool AccountDataChanged(string accountId, decimal[] values);
}

public class PlanningGridHub : Hub<IPlanningGridClient>
{
    public void SaveAccountData(string accountId, decimal[] values)
    {
        var result = Clients.Caller.AccountDataChanged(accountId, values);
    }
}

This code compiles pretty good, but if you execute the code, you’ll run into an exception. The screenshot below shows that exception: The return value of a client method must be void or of type Task.

http://robinsedlaczek.files.wordpress.com/2014/09/exceptiononreturnvalue.png?w=722&h=407

Exception on Return Value

So let’s go one step further and have a look into the source code of SignalR. SignalR uses a very flexible, extensible and modular pipeline under the hood. The hub pipeline. The hub pipeline consists of various modules through which are messages are passed. In both directions. A pipeline module implements the interface IHubPipelineModule. This interface defines methods for incoming and outgoing messages, as well as some methods for connection-related tasks such as connecting, reconnecting and so on. But let’s have a look at the method that is defined for calling methods at the client:BuildOutgoing(). The code below shows the definition.

/// <summary>
/// An <see cref="IHubPipelineModule"/> can intercept and customize various stages of hub processing such as connecting,
/// reconnecting, disconnecting, invoking server-side hub methods, invoking client-side hub methods, authorizing hub
/// clients and rejoining hub groups.
/// Modules can be be activated by calling <see cref="IHubPipeline.AddModule"/>.
/// The combined modules added to the <see cref="IHubPipeline" /> are invoked via the <see cref="IHubPipelineInvoker"/>
/// interface.
/// </summary>
public interface IHubPipelineModule
{
    /// <summary>
    /// Wraps a function that invokes a client-side hub method.
    /// </summary>
    /// <param name="send">A function that invokes a client-side hub method.</param>
    /// <returns>A wrapped function that invokes a client-side hub method.</returns>
    Func<IHubOutgoingInvokerContext, Task> BuildOutgoing(Func<IHubOutgoingInvokerContext, Task> send);

    ...
}

You can see that this method gets a function as an input parameter and returns another function with the same generic parameters. With that definition, the pipeline is build. Messages will be passed from one pipeline module to the next module and the implementation of each function works on the message. The return type of each function is Task. No place for returns values that could be typed as the generic type in Task of Tor something like that. The return type Task is only to get the pipeline working asynchronous. And that corresponds with the exception above, that the return type of client-side methods must be void or Task.

Let’s have another look at the client side. At clients, you can use the On-method to “listen” for method calls that come from the server. Consider the following code from my demo project, that implements the AccountDataChanged-method that is called from the server.

_proxy.On<string, decimal[]>("AccountDataChanged", (accountId, values) =>
{
    var row = new AccountData()
    {
        AccountId = accountId,
        Jan = values[0], Feb = values[1], Mar = values[2], Apr = values[3], May = values[4], Jun = values[5],
        Jul = values[6], Aug = values[7], Sep = values[8], Oct = values[9], Nov = values[10], Dec = values[11]
    };
 
    Dispatcher.Invoke(() => AccountDataRows.Add(row));
});

The On-method takes a string value that represents the client-side method name and a Lambda-expression that acts as the delegate that is called when the server calls the client method. The Lambda-expression takes two parameters that are defined by the generic parameters of the On-method. This parameters represents the signature of the client-side method. You can see that these are input parameters (please see the server code above). The Lambda-expression represents an Action. And actions do not have return types. Below you can see all overloads of the On-method taken from the SignalR source code. These methods are defined as extension methods on IHubProxy. Hub proxies are used for communication with the server at the client side. There are actions only. So the client side cannot implement methods the return any values.

public static class HubProxyExtensions
{
    public static T GetValue<T>(this IHubProxy proxy, string name);
    public static IObservable<IList<JToken>> Observe(this IHubProxy proxy, string eventName);
    public static IDisposable On(this IHubProxy proxy, string eventName, [Dynamic(new[] { false, true })]Action<dynamic> onData);
    public static IDisposable On(this IHubProxy proxy, string eventName, Action onData);
    public static IDisposable On<T>(this IHubProxy proxy, string eventName, Action<T> onData);
    public static IDisposable On<T1, T2>(this IHubProxy proxy, string eventName, Action<T1, T2> onData);
    public static IDisposable On<T1, T2, T3>(this IHubProxy proxy, string eventName, Action<T1, T2, T3> onData);
    public static IDisposable On<T1, T2, T3, T4>(this IHubProxy proxy, string eventName, Action<T1, T2, T3, T4> onData);
    public static IDisposable On<T1, T2, T3, T4, T5>(this IHubProxy proxy, string eventName, Action<T1, T2, T3, T4, T5> onData);
    public static IDisposable On<T1, T2, T3, T4, T5, T6>(this IHubProxy proxy, string eventName, Action<T1, T2, T3, T4, T5, T6> onData);
    public static IDisposable On<T1, T2, T3, T4, T5, T6, T7>(this IHubProxy proxy, string eventName, Action<T1, T2, T3, T4, T5, T6, T7> onData);
}

So far about the internals of SignalR. Now, the questions is, why did the SignalR team choose this framework design approach? In my opinion, they had simply no other choice. Please let me explain why they choose this design and why SignalR does not support method return values on the client-side. Better, I try to explain, because maybe there are other reasons and I did not got them. But here is my guess:

SignalR is a framework for real-time web communication. Therefore, the framework encapsulates underlying communication protocols and technologies. The user of the framework does not need to handle technical problems on transport layer. Instead, he can concentrate on his application. SignalR uses newest web communication standards under the hood and the development team around SignalR will maintain them. So, if new standards evolve some day, SignalR will cover it and applications using SignalR do not need to change their app implementations. As far as the API of SignalR does not change, applications do not need to be changed. They simply run on a newer version of SignalR.

To answer the question above, we have to look at the underlying protocols that are used in SignalR. Currently there are 4 technologies: Web Sockets, Server Sent Events (SSE), Long Polling and Forever Frame. The framework automatically choose the best technology when you use it depending on the operating system, browser, .Net Framework version and so on. It is completely transparent for the application that uses SignalR. The application uses the same API – always. To ensure that the SignalR API works always in the same way, the framework can only use the most common subset of features and limitations that are given by all the underlying technologies. And that is the crux of the matter (from my point of view).

If you look e.g. at long polling. When long polling is used, the client sends a request to the server. Then the server waits until new information is available and then returns with the response to the client. After the response arrived at the client, the client starts a new request (to simulate a persistent connection). So if the client sends a request to the server (and with that, calls a method at the server), the server can process the request (and execute the method) and send back the response that contains the return value from the method. The server can only call a client method within a response. After the response, there is no way to send a return value to the server within the same context. Of course, the client can send a next request that contains the return value from the previous method call, but what if the client has been shut down? No reliable answer/return value will arrive at the server.

The same is valid for Forever Frame that is proprietary to Microsoft. The client sends one request after the other to “establish” a persistent connection. So it’s similar to long polling.

Let’s have a look at Server Send Events (SSE). SSE are designed to have one-way communication – from the server to the client. Therefore, the client sends a request and opens a connection to the server. Then, the server sends events to the client. This event stream is one-way and read-only for the client. There is no chance for the client to send “answers” (and so return values) to the server.

Web Sockets on the other side are designed to use for two-way communication. But as stated above, SignalR uses the most common subset of features of all underlying technologies. And so the way back to the server with Web Sockets cannot be used by the framework. It would run in some environments but not in all. Microsoft could implement some workarounds, e.g. the client could send the return value in a second request. But then the framework has to deal with broken connections etc. a little bit more. So there would be a higher effort to gain reliability. Further, multiple client calls, as they are send over the MultipleSignalProxy class, require management of multiple return values. Return values must be identifiable by their clients they are coming from. So there is a lot of work that has to be done.

Please follow this link for brief descriptions of the underlying technologies.

Broken Connections

Another question during my talk on SignalR was about broken connections and how they are handled. SignalR is designed to establish “persistent connections”. You should notice, that SignalR differentiates between three types of connections: SignalR connections, transport connections and physical connections. When a lower connection level drops the connection, SignalR won’t, because it has all information to try to re-establish the connection. From the framework’s user point of view connections are persistent in that way, that the connection id won’t change if the connection needs to be re-established. So it feels like have a persistent connection. The feeling of a persistent connection is valid for group assignments, too. If a client belongs to a group and the connection has been re-established, SignalR automatically rejoins the client to the group again.

Internally, SignalR uses keep alive mechanisms to gain persistent connections. For more details I like to refer to the following article. It describes SignalR connection lifetime in-deep: SignalR documentation for connection lifetime. Connection lifetime management can be adjusted by setting timeout configurations of SignalR. Please consider the following code for an example. I take this code from the SignalR documentation (you can find the link above).

protected void Application_Start(object sender, EventArgs e)
{
    // Make long polling connections wait a maximum of 110 seconds for a
    // response. When that time expires, trigger a timeout command and
    // make the client reconnect.
    GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
    
    // Wait a maximum of 30 seconds after a transport connection is lost
    // before raising the Disconnected event to terminate the SignalR connection.
    GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(30);
    
    // For transports other than long polling, send a keepalive packet every
    // 10 seconds. 
    // This value must be no more than 1/3 of the DisconnectTimeout value.
    GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(10);
    
    RouteTable.Routes.MapHubs();
}

Security

First of all: Use SSL! SSL secures the transport of data and it should be your first-class citizen when thinking about security in your web applications. And it is only a question of configuration of the web server. Do SSL first, then think about further security concepts in your application.

SignalR does not provide an own authentication mechanism. Instead, it uses the authentication mechanism of your web application (e.g. Forms Authentication). So your authentication mechanism provides the identity of the user and then you can authorize the user do call hub methods or prohibit it. Therefore, SignalR provides the Authroize-attribute. You can use this attribute on methods to allow or prohibit users from calling these methods. You can use this attribute on the hub class, too. Then it is valid for all method in the hub. To configure that authentication is required for all hub methods, you can use the RequireAuthentication-method on the hub pipeline as shown below.

public partial class Startup 
{
    public void Configuration(IAppBuilder app) 
    {
        app.MapSignalR();
        GlobalHost.HubPipeline.RequireAuthentication();
    }
}

Then use the Authorize-attribute to give specific user and/or roles access to hub methods. For more details I like to refer to the SignalR documentation: SignalR documentation for authorization.

Internally, SignalR works with connection tokes. After a client has opened a connection to a hub, SignalR gets the user name from the authentication system of your web application, generates a unique connection id and creates a connection token with this information. All following requests from the client contain this token. SignalR then validates the token: it checks if the connection id belongs to the user. If not, an exception is thrown. For validation, SignalR stores the connection token at the server side and compares it with the token that comes with a following client request.

Authentication is authorization is a complex topic. When developers ask me about security I prefer to refer to the guys of Thinktecture. They provide open source software components that handle all the security stuff, e.g. the IdentityServer the let you store and manage your users identities. They provide a rich set of (video) tutorials and blog posts. They are the leading experts for security in .Net (in my humble opinion). It is worth to have a look. They also provide consulting services. Do not hesitate to contact them if you have any question.

Conclusion

I hope this post answers some questions about SignalR and helps you implementing your applications. If you have any question, please do not hesitate to contact me. I would appreciate if I can help you!

You can find the SignalR demo project from my lecture on GitHub. The slides are online on SlideShare. And please do not forget to like The Berlin Microsoft Connection on facebook.

This article was published at Robin Sedlaczek's Blog.