SignalR with external assemblies and obfuscators

Developing with SignalR is a very interesting experience. The technology, which allows us to create solutions that interact in real time with the browser and native apps, provides us with several layers of abstraction over technologies such as WebSockets, so that we can focus on the specific features of our apps. However, sometimes these abstractions may be a problem more than a solution.

One of the cases we can see this is with the hubs. A hub is a class that, when processed by signalR, is exposed to the browser by using Javascript. Unlike a REST API, a hub does not (only) responds to a request, they can also send messages both to the client who has made the request, the rest of the clients connected, or all of them, in real time.

To define a hub, we only need a class that inherits from the Hub class and SignalR will do the rest. It will discover (by using reflection) all the different hubs in the current assembly and generate the corresponding javascript.

Here is an example with the following code in C#:

public class ChatHub : Hub
    {
        public void Send(string name, string message)
        {
            // Call the broadcastMessage method to update clients.
            Clients.All.broadcastMessage(name, message);
        }
    }

The generated code by SignalR looks a little bit like this:

 proxies.chatHub = this.createHubProxy('chatHub');
        proxies.chatHub.client = { };
        proxies.chatHub.server = {
            send: function (name, message) {
                return proxies.chatHub.invoke.apply(proxies.chatHub, $.merge(["Send"], $.makeArray(arguments)));
             }
        };

As I mentioned earlier, signalR hubs are discovered automatically for the current assembly. What hapens if we store our hubs on a different asembly, for example, if we have two apps who use the same hub? After searching, I found the solution on StackOverflow. As I only needed to load one specific assembly, I picked only this line:

AppDomain.CurrentDomain.Load(typeof(Namespace.ChatHub).Assembly.FullName);

This code loads the Assembly that contains the class so SignalR can «discover» the Hub, we must place it just before the app.MapSignalR() or otherwise the Hub will not be generated.

Obfuscation

If we distribute our application our clients (for example when offering a solution containing SignalR so that they can install it on a local server) is usual to use tools like SmartAssembly or Dotfuscator, to protect the intellectual property of your code.

For a correct SignalR operation, it’s important to remember that we must not obfuscate, or prune the Hub classes, since the class is loaded by reflection. The obfuscation result will rename the classes and probably remove uncalled code (that is, in fact, called from Javascript). The best solution is to use hubs as a ‘proxy’ class and obfuscate those classes and methods called from the hub.

To avoid obfuscation you can can, either manually in the properties of the project, or through attributes. For SmartAssembly we find attributes such as [DoNotObfuscateType] and [DoNotPruneType] which apply to the whole class, and in the case of Dotfuscator we can use the [Obsufcation] attribute.

More information

Un tip rápido: SignalR, ensamblados externos y ofuscación

Desarrollar con SignalR es una experiencia muy interesante. La tecnología, que nos permite crear soluciones que interactúen en tiempo real con el navegador y con aplicaciones nativas, nos proporciona varias capas de abstracción sobre tecnologías como WebSockets, para que podamos centrarnos en el contenido de nuestras aplicaciones. Sin embargo, a veces los árboles no dejan ver el bosque.

Uno de los ejemplos se nos da con los hubs. Un hub es una clase que signalR expone al navegador mediante Javascript. A diferencia de una API REST, un hub no (solamente) responde a una petición, sino que puede enviar mensajes tanto al cliente que la ha realizado, a todos los clientes o al resto, en tiempo real, entre otras cosas.

Para definir un hub, solamente necesitamos una clase que herede de la clase Hub y SignalR hará el resto, es decir, descubrir los diferentes hubs y exponerlos al cliente.

Veamos un ejemplo, con el siguiente código en C#:


public class ChatHub : Hub
    {
        public void Send(string name, string message)
        {
            // Call the broadcastMessage method to update clients.
            Clients.All.broadcastMessage(name, message);
        }
    }

SignalR genera este código, que lo podemos ver acudiendo a la url http://miservicio/signalr/hubs:

 proxies.chatHub = this.createHubProxy('chatHub'); 
        proxies.chatHub.client = { };
        proxies.chatHub.server = {
            send: function (name, message) {
                return proxies.chatHub.invoke.apply(proxies.chatHub, $.merge(["Send"], $.makeArray(arguments)));
             }
        };

El hecho de que SignalR descubra de manera automática los hubs nos puede traer problemas cuando los hubs están en un ensamblado diferente al que está cargando SignalR (por ejemplo, si tenemos dos aplicaciones que usen exactamente el mismo tipo de hub).

Tras mucho buscar, la única solución que he encontrado para cargar los hubs desde otro ensamblado pasa por la siguiente línea:

AppDomain.CurrentDomain.Load(typeof(Namespace.ChatHub).Assembly.FullName);

Este código carga el ensamblado que contiene la clase para que SignalR «descubra» el Hub, y lo debemos situar justo antes de app.MapSignalR() o de lo contrario el Hub no se generará.

Ofuscación

Si distribuimos nuestra aplicación a nuestros clientes (por ejemplo ofrecemos una solución que contiene SignalR para que la puedan instalar en un servidor local) es habitual recurrir a herramientas como SmartAssembly o Dotfuscator, para proteger la propiedad intelectual de nuestro código.

Para que el funcionamiento de SignalR sea correcto, es importante recordar que no debemos ofuscar las clases Hub, ya que la clase se carga mediante reflexión, y la ofuscación provoca o bien que la clase no sea encontrada con su nombre original, o bien que no se pueda acceder a los métodos, o bien que, al no ser llamados por otras partes de nuestro código, se deseche. La mejor solución pasa por usar los hubs como una clase «pantalla» y ofuscar aquellas que sean llamadas desde el hub.

Para evitar la ofuscación podemos, o bien de manera manual en las propiedades del proyecto, o a través de atributos. En el caso de SmartAssembly encontramos atributos como [DoNotObfuscateType] y [DoNotPruneType] que se aplican a toda la clase, y en el caso de Dotfuscator contamos con el atributo [Obsufcation]

Más información