AspNetCore.Docs/aspnetcore/signalr/api-design.md

3.8 KiB

title author description monikerRange ms.author ms.custom ms.date uid
SignalR API design considerations anurse Learn how to design SignalR APIs for compatibility across versions of your app. >= aspnetcore-2.1 anurse mvc 11/06/2018 signalr/api-design

SignalR API design considerations

By Andrew Stanton-Nurse

This article provides guidance for building SignalR-based APIs.

Use custom object parameters to ensure backwards-compatibility

Adding parameters to a SignalR hub method (on either the client or the server) is a breaking change. This means older clients/servers will get errors when they try to invoke the method without the appropriate number of parameters. However, adding properties to a custom object parameter is not a breaking change. This can be used to design compatible APIs that are resilient to changes on the client or the server.

For example, consider a server-side API like the following:

[!code-csharpParameterBasedOldVersion]

The JavaScript client calls this method using invoke as follows:

[!code-typescriptCallWithOneParameter]

If you later add a second parameter to the server method, older clients won't provide this parameter value. For example:

[!code-csharpParameterBasedNewVersion]

When the old client tries to invoke this method, it will get an error like this:

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

On the server, you'll see a log message like this:

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

The old client only sent one parameter, but the newer server API required two parameters. Using custom objects as parameters gives you more flexibility. Let's redesign the original API to use a custom object:

[!code-csharpObjectBasedOldVersion]

Now, the client uses an object to call the method:

[!code-typescriptCallWithObject]

Instead of adding a parameter, add a property to the TotalLengthRequest object:

[!code-csharpObjectBasedNewVersion]

When the old client sends a single parameter, the extra Param2 property will be left null. You can detect a message sent by an older client by checking the Param2 for null and apply a default value. A new client can send both parameters.

[!code-typescriptCallWithObjectNew]

The same technique works for methods defined on the client. You can send a custom object from the server side:

[!code-csharpClientSideObjectBasedOld]

On the client side, you access the Message property rather than using a parameter:

[!code-typescriptOnWithObjectOld]

If you later decide to add the sender of the message to the payload, add a property to the object:

[!code-csharpClientSideObjectBasedNew]

The older clients won't be expecting the Sender value, so they'll ignore it. A new client can accept it by updating to read the new property:

[!code-typescriptOnWithObjectNew]

In this case, the new client is also tolerant of an old server that doesn't provide the Sender value. Since the old server won't provide the Sender value, the client checks to see if it exists before accessing it.