--- title: SignalR API design considerations author: bradygaster description: Learn how to design SignalR APIs for compatibility across versions of your app. monikerRange: '>= aspnetcore-2.1' ms.author: bradyg ms.custom: mvc ms.date: 11/12/2019 uid: signalr/api-design --- # SignalR API design considerations By [Andrew Stanton-Nurse](https://twitter.com/anurse) 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-csharp[ParameterBasedOldVersion](api-design/sample/Samples.cs?name=ParameterBasedOldVersion)] The JavaScript client calls this method using `invoke` as follows: [!code-typescript[CallWithOneParameter](api-design/sample/Samples.ts?name=CallWithOneParameter)] If you later add a second parameter to the server method, older clients won't provide this parameter value. For example: [!code-csharp[ParameterBasedNewVersion](api-design/sample/Samples.cs?name=ParameterBasedNewVersion)] 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-csharp[ObjectBasedOldVersion](api-design/sample/Samples.cs?name=ObjectBasedOldVersion)] Now, the client uses an object to call the method: [!code-typescript[CallWithObject](api-design/sample/Samples.ts?name=CallWithObject)] Instead of adding a parameter, add a property to the `TotalLengthRequest` object: [!code-csharp[ObjectBasedNewVersion](api-design/sample/Samples.cs?name=ObjectBasedNewVersion&highlight=4,9-13)] 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-typescript[CallWithObjectNew](api-design/sample/Samples.ts?name=CallWithObjectNew)] The same technique works for methods defined on the client. You can send a custom object from the server side: [!code-csharp[ClientSideObjectBasedOld](api-design/sample/Samples.cs?name=ClientSideObjectBasedOld)] On the client side, you access the `Message` property rather than using a parameter: [!code-typescript[OnWithObjectOld](api-design/sample/Samples.ts?name=OnWithObjectOld)] If you later decide to add the sender of the message to the payload, add a property to the object: [!code-csharp[ClientSideObjectBasedNew](api-design/sample/Samples.cs?name=ClientSideObjectBasedNew&highlight=5)] 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-typescript[OnWithObjectNew](api-design/sample/Samples.ts?name=OnWithObjectNew&highlight=2-5)] 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.