Use el Protocolo de concentrador MessagePack en SignalR para ASP.NET Core

En este artículo se supone que el lector está familiarizado con los temas tratados en Introducción a ASP.NET Core SignalR.

¿Qué es MessagePack?

MessagePack es un formato de serialización binaria rápido y compacto. Es útil cuando el rendimiento y el ancho de banda son una preocupación porque crea mensajes más pequeños que JSON. Los mensajes binarios son ilegibles al examinar los registros y los seguimientos de red a menos que los bytes se pasen a través de un analizador de MessagePack. SignalR tiene compatibilidad integrada con el formato MessagePack y proporciona API para que el cliente y el servidor lo usen.

Configurar MessagePack en el servidor

Para habilitar el Protocolo de concentrador MessagePack en el servidor, instale el paquete Microsoft.AspNetCore.SignalR.Protocols.MessagePack en su aplicación. En el método Startup.ConfigureServices, agregue AddMessagePackProtocol a la llamada AddSignalR para habilitar la compatibilidad con MessagePack en el servidor.

services.AddSignalR()
    .AddMessagePackProtocol();

Nota:

JSON está habilitado de manera predeterminada. La adición de MessagePack habilita la compatibilidad tanto con clientes de JSON como de MessagePack.

Para personalizar cómo MessagePack da formato a los datos, AddMessagePackProtocol toma un delegado para configurar las opciones. En ese delegado, la propiedad SerializerOptions se usa para configurar las opciones de serialización de MessagePack. Para más información sobre cómo funcionan los solucionadores, visite la biblioteca MessagePack en MessagePack-CSharp. Los atributos se pueden usar en los objetos que desea serializar para definir cómo se deben controlar.

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(new CustomResolver())
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

Advertencia

Se recomienda encarecidamente revisar CVE-2020-5234 y aplicar las revisiones recomendadas. Por ejemplo, llamar a .WithSecurity(MessagePackSecurity.UntrustedData) al reemplazar SerializerOptions.

Configuración de MessagePack en el cliente

Nota

JSON está habilitado de forma predeterminada para los clientes admitidos. Los clientes solo pueden admitir un único protocolo. Al agregar compatibilidad con MessagePack, se reemplazan los protocolos configurados anteriormente.

Cliente .NET

Para habilitar MessagePack en el cliente .NET, instale el paquete Microsoft.AspNetCore.SignalR.Protocols.MessagePack y llame a AddMessagePackProtocol en HubConnectionBuilder.

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chathub")
                        .AddMessagePackProtocol()
                        .Build();

Nota:

Esta llamada de AddMessagePackProtocol toma un delegado para configurar las opciones al igual que el servidor.

Cliente de JavaScript

El paquete npm @microsoft/signalr-protocol-msgpack proporciona compatibilidad con MessagePack para el cliente de JavaScript. Instale el paquete ejecutando el siguiente comando en un shell de comandos:

npm install @microsoft/signalr-protocol-msgpack

Después de instalar el paquete npm, el módulo se puede usar directamente a través de un cargador de módulos de JavaScript o importarlo en el explorador haciendo referencia al archivo siguiente:

node_modules\@microsoft\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

Se debe hacer referencia a los siguientes archivos javaScript necesarios en el orden que se muestra a continuación:

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

Al añadir .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol()) al HubConnectionBuilder se configura el cliente para que use el protocolo MessagePack cuando se conecte a un servidor.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

En este momento, no hay opciones de configuración para el protocolo MessagePack en el cliente de JavaScript.

Cliente de Java

Para habilitar MessagePack con Java, instale el paquete de com.microsoft.signalr.messagepack. Si usa Gradle, agregue la siguiente línea a la sección dependencies del archivo build.gradle:

implementation 'com.microsoft.signalr.messagepack:signalr-messagepack:5.0.0'

Si usa Maven, agregue las siguientes líneas dentro del elemento <dependencies> del archivo pom.xml:

<dependency>
    <groupId>com.microsoft.signalr.messagepack</groupId>
    <artifactId>signalr</artifactId>
    <version>5.0.0</version>
</dependency>

Llame a withHubProtocol(new MessagePackHubProtocol()) en HubConnectionBuilder.

HubConnection messagePackConnection = HubConnectionBuilder.create("YOUR HUB URL HERE")
    .withHubProtocol(new MessagePackHubProtocol())
    .build();

Consideraciones sobre MessagePack

Hay algunos problemas que se deben tener en cuenta al usar el protocolo de concentrador de MessagePack.

MessagePack distingue mayúsculas de minúsculas

El protocolo MessagePack distingue mayúsculas de minúsculas. Por ejemplo, considere la siguiente clase C#:

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

Cuando envíe desde el cliente de JavaScript, debe usar nombres de propiedades de PascalCased, ya que las mayúsculas y minúsculas deben coincidir exactamente con la clase de C#. Por ejemplo:

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

El uso de nombres de camelCased no se vinculará correctamente a la clase C#. Puede solucionar este problema usando el atributo Key para especificar un nombre diferente para la propiedad MessagePack. Para más información, consulte la documentación de MessagePack-CSharp.

DateTime.Kind no se conserva al serializar o deserializar

El protocolo MessagePack no proporciona una forma de codificar el valor Kind de un DateTime. Como resultado, al deserializar una fecha, el protocolo del concentrador de MessagePack se convertirá en el formato UTC si el DateTime.Kind es DateTimeKind.Local de lo contrario no tocará la hora y la pasará tal como está. Si trabaja con valores de DateTime, le recomendamos convertirlos a UTC antes de enviarlos. Conviértalos de UTC a hora local cuando los reciba.

Compatibilidad con MessagePack en el entorno de compilación "anticipada"

La biblioteca MessagePack-CSharp usada por el cliente y el servidor de .NET usa la generación de código para optimizar la serialización. Como resultado, no se admite de forma predeterminada en entornos que usan la compilación "anticipada" (por ejemplo, Xamarin iOS o Unity). Es posible usar MessagePack en estos entornos mediante la "generación previa" del código serializador o deserializador. Para más información, consulte la documentación de MessagePack-CSharp. Una vez que haya generado previamente los serializadores, puede registrarlos mediante el delegado de configuración pasado a AddMessagePackProtocol:

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        StaticCompositeResolver.Instance.Register(
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        );
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(StaticCompositeResolver.Instance)
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

Las comprobaciones de tipos son más estrictas en MessagePack

El protocolo de concentrador JSON realizará conversiones de tipo durante la deserialización. Por ejemplo, si el objeto entrante tiene un valor de propiedad que es un número ({ foo: 42 }), pero la propiedad de la clase .NET es de tipo string, el valor se convertirá. Sin embargo, MessagePack no realiza esta conversión y producirá una excepción que se puede ver en los registros del lado servidor (y en la consola):

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

Para obtener más información sobre esta limitación, consulte Problema de GitHub aspnet/SignalR#2937.

Caracteres y cadenas en Java

En el cliente de java, los objetos de char se serializarán como objetos de un carácter String. Esto contrasta con el cliente de C# y JavaScript, que los serializa como objetos short. La propia especificación MessagePack no define el comportamiento de los objetos de char, por lo que corresponde al autor de la biblioteca determinar cómo serializarlos. La diferencia de comportamiento entre nuestros clientes es el resultado de las bibliotecas que usamos para nuestras implementaciones.

Recursos adicionales

En este artículo se supone que el lector está familiarizado con los temas tratados en Introducción a ASP.NET Core SignalR.

¿Qué es MessagePack?

MessagePack es un formato de serialización binaria rápido y compacto. Es útil cuando el rendimiento y el ancho de banda son una preocupación porque crea mensajes más pequeños que JSON. Los mensajes binarios son ilegibles al examinar los registros y los seguimientos de red a menos que los bytes se pasen a través de un analizador de MessagePack. SignalR tiene compatibilidad integrada con el formato MessagePack y proporciona API para que el cliente y el servidor lo usen.

Configurar MessagePack en el servidor

Para habilitar el Protocolo de concentrador MessagePack en el servidor, instale el paquete Microsoft.AspNetCore.SignalR.Protocols.MessagePack en su aplicación. En el método Startup.ConfigureServices, agregue AddMessagePackProtocol a la llamada AddSignalR para habilitar la compatibilidad con MessagePack en el servidor.

Nota:

JSON está habilitado de manera predeterminada. La adición de MessagePack habilita la compatibilidad tanto con clientes de JSON como de MessagePack.

services.AddSignalR()
    .AddMessagePackProtocol();

Para personalizar cómo MessagePack da formato a los datos, AddMessagePackProtocol toma un delegado para configurar las opciones. En ese delegado, la propiedad SerializerOptions se puede usar para configurar las opciones de serialización de MessagePack. Para más información sobre cómo funcionan los solucionadores, visite la biblioteca MessagePack en MessagePack-CSharp. Los atributos se pueden usar en los objetos que desea serializar para definir cómo se deben controlar.

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(new CustomResolver())
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

Advertencia

Se recomienda encarecidamente revisar CVE-2020-5234 y aplicar las revisiones recomendadas. Por ejemplo, llamar a .WithSecurity(MessagePackSecurity.UntrustedData) al reemplazar SerializerOptions.

Configuración de MessagePack en el cliente

Nota

JSON está habilitado de forma predeterminada para los clientes admitidos. Los clientes solo pueden admitir un único protocolo. Al agregar compatibilidad con MessagePack, se reemplazarán los protocolos configurados anteriormente.

Cliente .NET

Para habilitar MessagePack en el cliente .NET, instale el paquete Microsoft.AspNetCore.SignalR.Protocols.MessagePack y llame a AddMessagePackProtocol en HubConnectionBuilder.

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chathub")
                        .AddMessagePackProtocol()
                        .Build();

Nota:

Esta llamada de AddMessagePackProtocol toma un delegado para configurar las opciones al igual que el servidor.

Cliente de JavaScript

El paquete npm @microsoft/signalr-protocol-msgpack proporciona compatibilidad con MessagePack para el cliente de JavaScript. Instale el paquete ejecutando el siguiente comando en un shell de comandos:

npm install @microsoft/signalr-protocol-msgpack

Después de instalar el paquete npm, el módulo se puede usar directamente a través de un cargador de módulos de JavaScript o importarlo en el explorador haciendo referencia al archivo siguiente:

node_modules\@microsoft\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

En un navegador, también debe hacerse referencia a la biblioteca de msgpack5. Use una etiqueta de <script> para crear una referencia. La biblioteca se puede encontrar en node_modules\msgpack5\dist\msgpack5.js.

Nota:

Al usar el elemento de <script>, el orden es importante. Si se hace referencia a signalr-protocol-msgpack.js antes que a msgpack5.js, se produce un error al intentar conectar con MessagePack. signalr.js también es necesario antes de signalr-protocol-msgpack.js.

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/msgpack5/msgpack5.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

Al añadir .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol()) al HubConnectionBuilder se configura el cliente para que use el protocolo MessagePack cuando se conecte a un servidor.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

Nota:

En este momento, no hay opciones de configuración para el protocolo MessagePack en el cliente de JavaScript.

Cliente de Java

Para habilitar MessagePack con Java, instale el paquete de com.microsoft.signalr.messagepack. Si usa Gradle, agregue la siguiente línea a la sección dependencies del archivo build.gradle:

implementation 'com.microsoft.signalr.messagepack:signalr-messagepack:5.0.0'

Si usa Maven, agregue las siguientes líneas dentro del elemento <dependencies> del archivo pom.xml:

<dependency>
    <groupId>com.microsoft.signalr.messagepack</groupId>
    <artifactId>signalr</artifactId>
    <version>5.0.0</version>
</dependency>

Llame a withHubProtocol(new MessagePackHubProtocol()) en HubConnectionBuilder.

HubConnection messagePackConnection = HubConnectionBuilder.create("YOUR HUB URL HERE")
    .withHubProtocol(new MessagePackHubProtocol())
    .build();

Consideraciones sobre MessagePack

Hay algunos problemas que se deben tener en cuenta al usar el protocolo de concentrador de MessagePack.

MessagePack distingue mayúsculas de minúsculas

El protocolo MessagePack distingue mayúsculas de minúsculas. Por ejemplo, considere la siguiente clase C#:

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

Cuando envíe desde el cliente de JavaScript, debe usar nombres de propiedades de PascalCased, ya que las mayúsculas y minúsculas deben coincidir exactamente con la clase de C#. Por ejemplo:

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

El uso de nombres de camelCased no se vinculará correctamente a la clase C#. Puede solucionar este problema usando el atributo Key para especificar un nombre diferente para la propiedad MessagePack. Para más información, consulte la documentación de MessagePack-CSharp.

DateTime.Kind no se conserva al serializar o deserializar

El protocolo MessagePack no proporciona una forma de codificar el valor Kind de un DateTime. Como resultado, al deserializar una fecha, el protocolo del concentrador de MessagePack se convertirá en el formato UTC si el DateTime.Kind es DateTimeKind.Local de lo contrario no tocará la hora y la pasará tal como está. Si trabaja con valores de DateTime, le recomendamos convertirlos a UTC antes de enviarlos. Conviértalos de UTC a hora local cuando los reciba.

DateTime.MinValue no es compatible con MessagePack en JavaScript

La biblioteca msgpack5 usada por el cliente SignalR de JavaScript no es compatible con el tipo timestamp96 de MessagePack. Este tipo se usa para codificar valores de fecha muy grandes (ya sea muy temprano en el pasado o muy lejos en el futuro). El valor de DateTime.MinValue es January 1, 0001, que se debe codificar en un valor timestamp96. Debido a esto, el envío de DateTime.MinValue a un cliente JavaScript no es compatible. Cuando DateTime.MinValue recibe el cliente de JavaScript, se produce el siguiente error:

Uncaught Error: unable to find ext type 255 at decoder.js:427

Normalmente, se usa DateTime.MinValue para codificar un valor "ausente" o null. Si necesita codificar ese valor en MessagePack, use un valor que admite un valor NULL DateTime (DateTime?) o codifique un valor independiente bool que indique si la fecha está presente.

Para más información sobre esta limitación, consulte Problema de GitHub aspnet/SignalR#2228.

Compatibilidad con MessagePack en el entorno de compilación "anticipada"

La biblioteca MessagePack-CSharp usada por el cliente y el servidor de .NET usa la generación de código para optimizar la serialización. Como resultado, no se admite de forma predeterminada en entornos que usan la compilación "anticipada" (por ejemplo, Xamarin iOS o Unity). Es posible usar MessagePack en estos entornos mediante la "generación previa" del código serializador o deserializador. Para más información, consulte la documentación de MessagePack-CSharp. Una vez que haya generado previamente los serializadores, puede registrarlos mediante el delegado de configuración pasado a AddMessagePackProtocol:

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        StaticCompositeResolver.Instance.Register(
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        );
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(StaticCompositeResolver.Instance)
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

Las comprobaciones de tipos son más estrictas en MessagePack

El protocolo de concentrador JSON realizará conversiones de tipo durante la deserialización. Por ejemplo, si el objeto entrante tiene un valor de propiedad que es un número ({ foo: 42 }), pero la propiedad de la clase .NET es de tipo string, el valor se convertirá. Sin embargo, MessagePack no realiza esta conversión y producirá una excepción que se puede ver en los registros del lado servidor (y en la consola):

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

Para obtener más información sobre esta limitación, consulte Problema de GitHub aspnet/SignalR#2937.

Caracteres y cadenas en Java

En el cliente de java, los objetos de char se serializarán como objetos de un carácter String. Esto contrasta con el cliente de C# y JavaScript, que los serializa como objetos short. La propia especificación MessagePack no define el comportamiento de los objetos de char, por lo que corresponde al autor de la biblioteca determinar cómo serializarlos. La diferencia de comportamiento entre nuestros clientes es el resultado de las bibliotecas que usamos para nuestras implementaciones.

Recursos adicionales

En este artículo se supone que el lector está familiarizado con los temas tratados en Introducción a ASP.NET Core SignalR.

¿Qué es MessagePack?

MessagePack es un formato de serialización binaria rápido y compacto. Es útil cuando el rendimiento y el ancho de banda son una preocupación porque crea mensajes más pequeños que JSON. Los mensajes binarios son ilegibles al examinar los registros y los seguimientos de red a menos que los bytes se pasen a través de un analizador de MessagePack. SignalR tiene compatibilidad integrada con el formato MessagePack y proporciona API para que el cliente y el servidor lo usen.

Configurar MessagePack en el servidor

Para habilitar el Protocolo de concentrador MessagePack en el servidor, instale el paquete Microsoft.AspNetCore.SignalR.Protocols.MessagePack en su aplicación. En el método Startup.ConfigureServices, agregue AddMessagePackProtocol a la llamada AddSignalR para habilitar la compatibilidad con MessagePack en el servidor.

Nota:

JSON está habilitado de manera predeterminada. La adición de MessagePack habilita la compatibilidad tanto con clientes de JSON como de MessagePack.

services.AddSignalR()
    .AddMessagePackProtocol();

Para personalizar cómo MessagePack da formato a los datos, AddMessagePackProtocol toma un delegado para configurar las opciones. En ese delegado, la propiedad FormatterResolvers se puede usar para configurar las opciones de serialización de MessagePack. Para más información sobre cómo funcionan los solucionadores, visite la biblioteca MessagePack en MessagePack-CSharp. Los atributos se pueden usar en los objetos que desea serializar para definir cómo se deben controlar.

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

Advertencia

Se recomienda encarecidamente revisar CVE-2020-5234 y aplicar las revisiones recomendadas. Por ejemplo, estableciendo la propiedad estática de MessagePackSecurity.Active en MessagePackSecurity.UntrustedData. Establecer el MessagePackSecurity.Active requiere instalar manualmente una versión 1.9.x de MessagePack. La instalación de MessagePack 1.9.x actualiza la versión que usa SignalR. La versión 2.x de MessagePack ha introducido cambios de última hora y es incompatible con las versiones 3.1 y anteriores de SignalR. Cuando MessagePackSecurity.Active no está establecido en MessagePackSecurity.UntrustedData, un cliente malintencionado podría provocar una denegación de servicio. Establezca MessagePackSecurity.Active en Program.Main, como se muestra en el siguiente código:

using MessagePack;

public static void Main(string[] args)
{
  MessagePackSecurity.Active = MessagePackSecurity.UntrustedData;

  CreateHostBuilder(args).Build().Run();
}

Configuración de MessagePack en el cliente

Nota

JSON está habilitado de forma predeterminada para los clientes admitidos. Los clientes solo pueden admitir un único protocolo. Al agregar compatibilidad con MessagePack, se reemplazarán los protocolos configurados anteriormente.

Cliente .NET

Para habilitar MessagePack en el cliente .NET, instale el paquete Microsoft.AspNetCore.SignalR.Protocols.MessagePack y llame a AddMessagePackProtocol en HubConnectionBuilder.

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chathub")
                        .AddMessagePackProtocol()
                        .Build();

Nota:

Esta llamada de AddMessagePackProtocol toma un delegado para configurar las opciones al igual que el servidor.

Cliente de JavaScript

El paquete npm @microsoft/signalr-protocol-msgpack proporciona compatibilidad con MessagePack para el cliente de JavaScript. Instale el paquete ejecutando el siguiente comando en un shell de comandos:

npm install @microsoft/signalr-protocol-msgpack

Después de instalar el paquete npm, el módulo se puede usar directamente a través de un cargador de módulos de JavaScript o importarlo en el explorador haciendo referencia al archivo siguiente:

node_modules\@microsoft\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

En un navegador, también debe hacerse referencia a la biblioteca de msgpack5. Use una etiqueta de <script> para crear una referencia. La biblioteca se puede encontrar en node_modules\msgpack5\dist\msgpack5.js.

Nota:

Al usar el elemento de <script>, el orden es importante. Si se hace referencia a signalr-protocol-msgpack.js antes que a msgpack5.js, se produce un error al intentar conectar con MessagePack. signalr.js también es necesario antes de signalr-protocol-msgpack.js.

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/msgpack5/msgpack5.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

Al añadir .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol()) al HubConnectionBuilder se configura el cliente para que use el protocolo MessagePack cuando se conecte a un servidor.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

Nota:

En este momento, no hay opciones de configuración para el protocolo MessagePack en el cliente de JavaScript.

Consideraciones sobre MessagePack

Hay algunos problemas que se deben tener en cuenta al usar el protocolo de concentrador de MessagePack.

MessagePack distingue mayúsculas de minúsculas

El protocolo MessagePack distingue mayúsculas de minúsculas. Por ejemplo, considere la siguiente clase C#:

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

Cuando envíe desde el cliente de JavaScript, debe usar nombres de propiedades de PascalCased, ya que las mayúsculas y minúsculas deben coincidir exactamente con la clase de C#. Por ejemplo:

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

El uso de nombres de camelCased no se vinculará correctamente a la clase C#. Puede solucionar este problema usando el atributo Key para especificar un nombre diferente para la propiedad MessagePack. Para más información, consulte la documentación de MessagePack-CSharp.

DateTime.Kind no se conserva al serializar o deserializar

El protocolo MessagePack no proporciona una forma de codificar el valor Kind de un DateTime. Como resultado, al deserializar una fecha, el protocolo de concentrador de MessagePack asume que la fecha entrante está en formato UTC. Si trabaja con valores de DateTime en hora local, le recomendamos convertirlos a UTC antes de enviarlos. Conviértalos de UTC a hora local cuando los reciba.

Para más información sobre esta limitación, consulte Problema de GitHub aspnet/SignalR#2632.

DateTime.MinValue no es compatible con MessagePack en JavaScript

La biblioteca msgpack5 usada por el cliente SignalR de JavaScript no es compatible con el tipo timestamp96 de MessagePack. Este tipo se usa para codificar valores de fecha muy grandes (ya sea muy temprano en el pasado o muy lejos en el futuro). El valor de DateTime.MinValue es January 1, 0001, que se debe codificar en un valor timestamp96. Debido a esto, el envío de DateTime.MinValue a un cliente JavaScript no es compatible. Cuando DateTime.MinValue recibe el cliente de JavaScript, se produce el siguiente error:

Uncaught Error: unable to find ext type 255 at decoder.js:427

Normalmente, se usa DateTime.MinValue para codificar un valor "ausente" o null. Si necesita codificar ese valor en MessagePack, use un valor que admite un valor NULL DateTime (DateTime?) o codifique un valor independiente bool que indique si la fecha está presente.

Para más información sobre esta limitación, consulte Problema de GitHub aspnet/SignalR#2228.

Compatibilidad con MessagePack en el entorno de compilación "anticipada"

La biblioteca MessagePack-CSharp usada por el cliente y el servidor de .NET usa la generación de código para optimizar la serialización. Como resultado, no se admite de forma predeterminada en entornos que usan la compilación "anticipada" (por ejemplo, Xamarin iOS o Unity). Es posible usar MessagePack en estos entornos mediante la "generación previa" del código serializador o deserializador. Para más información, consulte la documentación de MessagePack-CSharp. Una vez que haya generado previamente los serializadores, puede registrarlos mediante el delegado de configuración pasado a AddMessagePackProtocol:

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

Las comprobaciones de tipos son más estrictas en MessagePack

El protocolo de concentrador JSON realizará conversiones de tipo durante la deserialización. Por ejemplo, si el objeto entrante tiene un valor de propiedad que es un número ({ foo: 42 }), pero la propiedad de la clase .NET es de tipo string, el valor se convertirá. Sin embargo, MessagePack no realiza esta conversión y producirá una excepción que se puede ver en los registros del lado servidor (y en la consola):

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

Para obtener más información sobre esta limitación, consulte Problema de GitHub aspnet/SignalR#2937.

Recursos adicionales

En este artículo se supone que el lector está familiarizado con los temas tratados en Introducción a ASP.NET Core SignalR.

¿Qué es MessagePack?

MessagePack es un formato de serialización binaria rápido y compacto. Es útil cuando el rendimiento y el ancho de banda son una preocupación porque crea mensajes más pequeños que JSON. Los mensajes binarios son ilegibles al examinar los registros y los seguimientos de red a menos que los bytes se pasen a través de un analizador de MessagePack. SignalR tiene compatibilidad integrada con el formato MessagePack y proporciona API para que el cliente y el servidor lo usen.

Configurar MessagePack en el servidor

Para habilitar el Protocolo de concentrador MessagePack en el servidor, instale el paquete Microsoft.AspNetCore.SignalR.Protocols.MessagePack en su aplicación. En el método Startup.ConfigureServices, agregue AddMessagePackProtocol a la llamada AddSignalR para habilitar la compatibilidad con MessagePack en el servidor.

Nota:

JSON está habilitado de manera predeterminada. La adición de MessagePack habilita la compatibilidad tanto con clientes de JSON como de MessagePack.

services.AddSignalR()
    .AddMessagePackProtocol();

Para personalizar cómo MessagePack da formato a los datos, AddMessagePackProtocol toma un delegado para configurar las opciones. En ese delegado, la propiedad FormatterResolvers se puede usar para configurar las opciones de serialización de MessagePack. Para más información sobre cómo funcionan los solucionadores, visite la biblioteca MessagePack en MessagePack-CSharp. Los atributos se pueden usar en los objetos que desea serializar para definir cómo se deben controlar.

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

Advertencia

Se recomienda encarecidamente revisar CVE-2020-5234 y aplicar las revisiones recomendadas. Por ejemplo, estableciendo la propiedad estática de MessagePackSecurity.Active en MessagePackSecurity.UntrustedData. Establecer el MessagePackSecurity.Active requiere instalar manualmente una versión 1.9.x de MessagePack. La instalación de MessagePack 1.9.x actualiza la versión que usa SignalR. Cuando MessagePackSecurity.Active no está establecido en MessagePackSecurity.UntrustedData, un cliente malintencionado podría provocar una denegación de servicio. Establezca MessagePackSecurity.Active en Program.Main, como se muestra en el siguiente código:

using MessagePack;

public static void Main(string[] args)
{
  MessagePackSecurity.Active = MessagePackSecurity.UntrustedData;

  CreateHostBuilder(args).Build().Run();
}

Configuración de MessagePack en el cliente

Nota

JSON está habilitado de forma predeterminada para los clientes admitidos. Los clientes solo pueden admitir un único protocolo. Al agregar compatibilidad con MessagePack, se reemplazarán los protocolos configurados anteriormente.

Cliente .NET

Para habilitar MessagePack en el cliente .NET, instale el paquete Microsoft.AspNetCore.SignalR.Protocols.MessagePack y llame a AddMessagePackProtocol en HubConnectionBuilder.

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chathub")
                        .AddMessagePackProtocol()
                        .Build();

Nota:

Esta llamada de AddMessagePackProtocol toma un delegado para configurar las opciones al igual que el servidor.

Cliente de JavaScript

El paquete npm @aspnet/signalr-protocol-msgpack proporciona compatibilidad con MessagePack para el cliente de JavaScript. Instale el paquete ejecutando el siguiente comando en un shell de comandos:

npm install @aspnet/signalr-protocol-msgpack

Después de instalar el paquete npm, el módulo se puede usar directamente a través de un cargador de módulos de JavaScript o importarlo en el explorador haciendo referencia al archivo siguiente:

node_modules\@aspnet\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

En un navegador, también debe hacerse referencia a la biblioteca de msgpack5. Use una etiqueta de <script> para crear una referencia. La biblioteca se puede encontrar en node_modules\msgpack5\dist\msgpack5.js.

Nota:

Al usar el elemento de <script>, el orden es importante. Si se hace referencia a signalr-protocol-msgpack.js antes que a msgpack5.js, se produce un error al intentar conectar con MessagePack. signalr.js también es necesario antes de signalr-protocol-msgpack.js.

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/msgpack5/msgpack5.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

Al añadir .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol()) al HubConnectionBuilder se configura el cliente para que use el protocolo MessagePack cuando se conecte a un servidor.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

Nota:

En este momento, no hay opciones de configuración para el protocolo MessagePack en el cliente de JavaScript.

Consideraciones sobre MessagePack

Hay algunos problemas que se deben tener en cuenta al usar el protocolo de concentrador de MessagePack.

MessagePack distingue mayúsculas de minúsculas

El protocolo MessagePack distingue mayúsculas de minúsculas. Por ejemplo, considere la siguiente clase C#:

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

Cuando envíe desde el cliente de JavaScript, debe usar nombres de propiedades de PascalCased, ya que las mayúsculas y minúsculas deben coincidir exactamente con la clase de C#. Por ejemplo:

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

El uso de nombres de camelCased no se vinculará correctamente a la clase C#. Puede solucionar este problema usando el atributo Key para especificar un nombre diferente para la propiedad MessagePack. Para más información, consulte la documentación de MessagePack-CSharp.

DateTime.Kind no se conserva al serializar o deserializar

El protocolo MessagePack no proporciona una forma de codificar el valor Kind de un DateTime. Como resultado, al deserializar una fecha, el protocolo de concentrador de MessagePack asume que la fecha entrante está en formato UTC. Si trabaja con valores de DateTime en hora local, le recomendamos convertirlos a UTC antes de enviarlos. Conviértalos de UTC a hora local cuando los reciba.

Para más información sobre esta limitación, consulte Problema de GitHub aspnet/SignalR#2632.

DateTime.MinValue no es compatible con MessagePack en JavaScript

La biblioteca msgpack5 usada por el cliente SignalR de JavaScript no es compatible con el tipo timestamp96 de MessagePack. Este tipo se usa para codificar valores de fecha muy grandes (ya sea muy temprano en el pasado o muy lejos en el futuro). El valor de DateTime.MinValue es January 1, 0001, que se debe codificar en un valor timestamp96. Debido a esto, el envío de DateTime.MinValue a un cliente JavaScript no es compatible. Cuando DateTime.MinValue recibe el cliente de JavaScript, se produce el siguiente error:

Uncaught Error: unable to find ext type 255 at decoder.js:427

Normalmente, se usa DateTime.MinValue para codificar un valor "ausente" o null. Si necesita codificar ese valor en MessagePack, use un valor que admite un valor NULL DateTime (DateTime?) o codifique un valor independiente bool que indique si la fecha está presente.

Para más información sobre esta limitación, consulte Problema de GitHub aspnet/SignalR#2228.

Compatibilidad con MessagePack en el entorno de compilación "anticipada"

La biblioteca MessagePack-CSharp usada por el cliente y el servidor de .NET usa la generación de código para optimizar la serialización. Como resultado, no se admite de forma predeterminada en entornos que usan la compilación "anticipada" (por ejemplo, Xamarin iOS o Unity). Es posible usar MessagePack en estos entornos mediante la "generación previa" del código serializador o deserializador. Para más información, consulte la documentación de MessagePack-CSharp. Una vez que haya generado previamente los serializadores, puede registrarlos mediante el delegado de configuración pasado a AddMessagePackProtocol:

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

Las comprobaciones de tipos son más estrictas en MessagePack

El protocolo de concentrador JSON realizará conversiones de tipo durante la deserialización. Por ejemplo, si el objeto entrante tiene un valor de propiedad que es un número ({ foo: 42 }), pero la propiedad de la clase .NET es de tipo string, el valor se convertirá. Sin embargo, MessagePack no realiza esta conversión y producirá una excepción que se puede ver en los registros del lado servidor (y en la consola):

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

Para obtener más información sobre esta limitación, consulte Problema de GitHub aspnet/SignalR#2937.

Recursos adicionales