Condividi tramite


SignalR Considerazioni sulla progettazione delle API

Di Andrew Stanton-Nurse

Questo articolo fornisce indicazioni per la creazione SignalRdi API basate su .

Usare parametri di oggetto personalizzati per garantire la compatibilità con le versioni precedenti

L'aggiunta di parametri a un SignalR metodo hub (nel client o nel server) è una modifica che causa un'interruzione. Ciò significa che i client o i server meno recenti riceveranno errori quando tentano di richiamare il metodo senza il numero appropriato di parametri. Tuttavia, l'aggiunta di proprietà a un parametro oggetto personalizzato non è una modifica che causa un'interruzione. Può essere usato per progettare API compatibili resilienti alle modifiche nel client o nel server.

Si consideri ad esempio un'API lato server simile alla seguente:

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

Il client JavaScript chiama questo metodo usando invoke come segue:

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

Se successivamente si aggiunge un secondo parametro al metodo server, i client meno recenti non forniranno questo valore di parametro. Ad esempio:

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

Quando il client precedente tenta di richiamare questo metodo, viene visualizzato un errore simile al seguente:

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

Nel server verrà visualizzato un messaggio di log simile al seguente:

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

Il client precedente ha inviato un solo parametro, ma l'API server più recente richiedeva due parametri. L'uso di oggetti personalizzati come parametri offre maggiore flessibilità. Verrà ora riprogettata l'API originale per usare un oggetto personalizzato:

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

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

Il client usa ora un oggetto per chiamare il metodo :

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

Anziché aggiungere un parametro, aggiungere una proprietà all'oggetto 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;
}

Quando il client precedente invia un singolo parametro, la proprietà aggiuntiva Param2 verrà lasciata null. È possibile rilevare un messaggio inviato da un client precedente controllando e Param2 null applicando un valore predefinito. Un nuovo client può inviare entrambi i parametri.

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

La stessa tecnica funziona per i metodi definiti nel client. È possibile inviare un oggetto personalizzato dal lato server:

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

Sul lato client è possibile accedere alla Message proprietà anziché usare un parametro :

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

Se successivamente si decide di aggiungere il mittente del messaggio al payload, aggiungere una proprietà all'oggetto :

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

I client meno recenti non prevedono il Sender valore, quindi lo ignoreranno. Un nuovo client può accettarlo aggiornandolo per leggere la nuova proprietà:

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

In questo caso, il nuovo client è anche tollerante di un server precedente che non fornisce il Sender valore. Poiché il server precedente non fornirà il Sender valore, il client verifica se esiste prima di accedervi.

Risorse aggiuntive