Compartir a través de


Consideraciones de diseño de API de SignalR

Por Andrew Stanton-Nurse

En este artículo se proporcionan instrucciones para crear API basadas en SignalR.

Uso de parámetros de objeto personalizados para garantizar la compatibilidad con versiones anteriores

Agregar parámetros a un método de concentrador de SignalR (en el cliente o en el servidor) es un cambio importante. Esto significa que los clientes o servidores más antiguos obtendrán errores cuando intenten invocar el método sin el número adecuado de parámetros. Sin embargo, agregar propiedades a un parámetro de objeto personalizado no es un cambio importante. Esto se puede usar para diseñar API compatibles que sean resistentes a los cambios en el cliente o el servidor.

Por ejemplo, considere una API del lado servidor como la siguiente:

public int GetTotalLength(string param1)
{
    return param1.Length;
}

El cliente de JavaScript llama a este método mediante invoke como se indica a continuación:

connection.invoke("GetTotalLength", "value1");

Si posteriormente agrega un segundo parámetro al método de servidor, los clientes más antiguos no proporcionarán este valor de parámetro. Por ejemplo:

public int GetTotalLength(string param1, string param2)
{
    return param1.Length + param2.Length;
}

Cuando el cliente anterior intenta invocar este método, obtendrá un error similar al siguiente:

Microsoft.AspNetCore.SignalR.HubException: Failed to invoke 'GetTotalLength' due to an error on the server.

En el servidor, verá un mensaje de registro similar al siguiente:

System.IO.InvalidDataException: Invocation provides 1 argument(s) but target expects 2.

El cliente anterior solo envió un parámetro, pero la API de servidor más reciente requería dos parámetros. El uso de objetos personalizados como parámetros proporciona más flexibilidad. Vamos a rediseñar la API original para usar un objeto personalizado:

public class TotalLengthRequest
{
    public string Param1 { get; set; }
}

public int GetTotalLength(TotalLengthRequest req)
{
    return req.Param1.Length;
}

Ahora, el cliente usa un objeto para llamar al método:

connection.invoke("GetTotalLength", { param1: "value1" });

En lugar de agregar un parámetro, agregue una propiedad al objeto TotalLengthRequest:

public class TotalLengthRequest
{
    public string Param1 { get; set; }
    public string Param2 { get; set; }
}

public int GetTotalLength(TotalLengthRequest req)
{
    var length = req.Param1.Length;
    if (req.Param2 != null)
    {
        length += req.Param2.Length;
    }
    return length;
}

Cuando el cliente anterior envía un único parámetro, la propiedad adicional Param2 se dejará como null. Puede detectar un mensaje enviado por un cliente anterior comprobando el Param2 para null y aplicando un valor predeterminado. Un nuevo cliente puede enviar ambos parámetros.

connection.invoke("GetTotalLength", { param1: "value1", param2: "value2" });

La misma técnica funciona para los métodos definidos en el cliente. Puede enviar un objeto personalizado desde el lado servidor:

public async Task Broadcast(string message)
{
    await Clients.All.SendAsync("ReceiveMessage", new
    {
        Message = message
    });
}

En el lado cliente, se accede a la propiedad Message en lugar de usar un parámetro:

connection.on("ReceiveMessage", (req) => {
    appendMessageToChatWindow(req.message);
});

Si más adelante decide agregar el remitente del mensaje a la carga, agregue una propiedad al objeto:

public async Task Broadcast(string message)
{
    await Clients.All.SendAsync("ReceiveMessage", new
    {
        Sender = Context.User.Identity.Name,
        Message = message
    });
}

Los clientes más antiguos no esperarán el valor Sender, por lo que lo omitirán. Un nuevo cliente puede aceptarlo si se actualiza para leer la nueva propiedad:

connection.on("ReceiveMessage", (req) => {
    let message = req.message;
    if (req.sender) {
        message = req.sender + ": " + message;
    }
    appendMessageToChatWindow(message);
});

En este caso, el nuevo cliente también es tolerante a un servidor antiguo que no proporciona el valor Sender. Dado que el servidor anterior no proporcionará el valor Sender, el cliente comprueba si existe antes de acceder a él.

Recursos adicionales