Partager via


SignalR Considérations relatives à la conception d’API

Par Andrew Stanton-Nurse

Cet article fournit des conseils pour la création d'API basées sur SignalR.

Utiliser des paramètres d’objet personnalisés pour garantir la compatibilité descendante

L’ajout de paramètres à une SignalR méthode hub (sur le client ou le serveur) est un changement de rupture. Cela signifie que les anciens clients/serveurs obtiennent des erreurs lorsqu’ils essaient d’appeler la méthode sans le nombre approprié de paramètres. Toutefois, l’ajout de propriétés à un paramètre d’objet personnalisé n’est pas un changement disruptif. Cela peut être utilisé pour concevoir des API compatibles résilientes aux modifications sur le client ou le serveur.

Par exemple, considérez une API côté serveur comme suit :

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

Le client JavaScript appelle cette méthode à l’aide de invoke comme suit :

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

Si vous ajoutez ultérieurement un deuxième paramètre à la méthode de serveur, les anciens clients ne fournissent pas cette valeur de paramètre. Par exemple:

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

Lorsque l’ancien client tente d’appeler cette méthode, une erreur semblable à celle-ci s’affiche :

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

Sur le serveur, un message de journal s’affiche comme suit :

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

L’ancien client n’a envoyé qu’un seul paramètre, mais l’API de serveur plus récente nécessite deux paramètres. L’utilisation d’objets personnalisés en tant que paramètres vous offre plus de flexibilité. Nous allons redéfinir l’API d’origine pour utiliser un objet personnalisé :

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

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

À présent, le client utilise un objet pour appeler la méthode :

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

Au lieu d’ajouter un paramètre, ajoutez une propriété à l’objet 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;
}

Lorsque l’ancien client envoie un paramètre unique, la propriété supplémentaire Param2 est laissée null. Vous pouvez détecter un message envoyé par un ancien client en vérifiant le `Param2` pour `null` et en appliquant une valeur par défaut. Un nouveau client peut envoyer les deux paramètres.

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

La même technique fonctionne pour les méthodes définies sur le client. Vous pouvez envoyer un objet personnalisé côté serveur :

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

Côté client, vous accédez à la Message propriété plutôt qu’à l’aide d’un paramètre :

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

Si vous décidez ultérieurement d’ajouter l’expéditeur du message à la charge utile, ajoutez une propriété à l’objet :

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

Les clients plus anciens ne s’attendent pas à la Sender valeur, de sorte qu’ils l’ignorent. Un nouveau client peut l’accepter en le mettant à jour pour lire la nouvelle propriété :

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

Dans ce cas, le nouveau client est également tolérant à un ancien serveur qui ne fournit pas la Sender valeur. Étant donné que l’ancien serveur ne fournit pas la Sender valeur, le client vérifie s’il existe avant de l’accéder.

Ressources supplémentaires