diff --git a/src/SignalR/clients/csharp/Client.Core/src/HubConnectionExtensions.cs b/src/SignalR/clients/csharp/Client.Core/src/HubConnectionExtensions.cs index 47e87549f4..fc24655655 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/HubConnectionExtensions.cs +++ b/src/SignalR/clients/csharp/Client.Core/src/HubConnectionExtensions.cs @@ -242,5 +242,210 @@ namespace Microsoft.AspNetCore.SignalR.Client return currentHandler(parameters); }, handler); } + + /// + /// Registers a handler that will be invoked when the hub method with the specified method name is invoked. + /// + /// The hub connection. + /// The name of the hub method to define. + /// The handler that will be raised when the hub method is invoked. + /// A subscription that can be disposed to unsubscribe from the hub method. + public static IDisposable On(this HubConnection hubConnection, string methodName, Func handler) + { + if (hubConnection == null) + { + throw new ArgumentNullException(nameof(hubConnection)); + } + + return hubConnection.On(methodName, Type.EmptyTypes, args => handler()); + } + + /// + /// Registers a handler that will be invoked when the hub method with the specified method name is invoked. + /// + /// The first argument type. + /// The hub connection. + /// The name of the hub method to define. + /// The handler that will be raised when the hub method is invoked. + /// A subscription that can be disposed to unsubscribe from the hub method. + public static IDisposable On(this HubConnection hubConnection, string methodName, Func handler) + { + if (hubConnection == null) + { + throw new ArgumentNullException(nameof(hubConnection)); + } + + return hubConnection.On(methodName, + new[] { typeof(T1) }, + args => handler((T1)args[0])); + } + + /// + /// Registers a handler that will be invoked when the hub method with the specified method name is invoked. + /// + /// The first argument type. + /// The second argument type. + /// The hub connection. + /// The name of the hub method to define. + /// The handler that will be raised when the hub method is invoked. + /// A subscription that can be disposed to unsubscribe from the hub method. + public static IDisposable On(this HubConnection hubConnection, string methodName, Func handler) + { + if (hubConnection == null) + { + throw new ArgumentNullException(nameof(hubConnection)); + } + + return hubConnection.On(methodName, + new[] { typeof(T1), typeof(T2) }, + args => handler((T1)args[0], (T2)args[1])); + } + + /// + /// Registers a handler that will be invoked when the hub method with the specified method name is invoked. + /// + /// The first argument type. + /// The second argument type. + /// The third argument type. + /// The hub connection. + /// The name of the hub method to define. + /// The handler that will be raised when the hub method is invoked. + /// A subscription that can be disposed to unsubscribe from the hub method. + public static IDisposable On(this HubConnection hubConnection, string methodName, Func handler) + { + if (hubConnection == null) + { + throw new ArgumentNullException(nameof(hubConnection)); + } + + return hubConnection.On(methodName, + new[] { typeof(T1), typeof(T2), typeof(T3) }, + args => handler((T1)args[0], (T2)args[1], (T3)args[2])); + } + + /// + /// Registers a handler that will be invoked when the hub method with the specified method name is invoked. + /// + /// The first argument type. + /// The second argument type. + /// The third argument type. + /// The fourth argument type. + /// The hub connection. + /// The name of the hub method to define. + /// The handler that will be raised when the hub method is invoked. + /// A subscription that can be disposed to unsubscribe from the hub method. + public static IDisposable On(this HubConnection hubConnection, string methodName, Func handler) + { + if (hubConnection == null) + { + throw new ArgumentNullException(nameof(hubConnection)); + } + + return hubConnection.On(methodName, + new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, + args => handler((T1)args[0], (T2)args[1], (T3)args[2], (T4)args[3])); + } + + /// + /// Registers a handler that will be invoked when the hub method with the specified method name is invoked. + /// + /// The first argument type. + /// The second argument type. + /// The third argument type. + /// The fourth argument type. + /// The fifth argument type. + /// The hub connection. + /// The name of the hub method to define. + /// The handler that will be raised when the hub method is invoked. + /// A subscription that can be disposed to unsubscribe from the hub method. + public static IDisposable On(this HubConnection hubConnection, string methodName, Func handler) + { + if (hubConnection == null) + { + throw new ArgumentNullException(nameof(hubConnection)); + } + + return hubConnection.On(methodName, + new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, + args => handler((T1)args[0], (T2)args[1], (T3)args[2], (T4)args[3], (T5)args[4])); + } + + /// + /// Registers a handler that will be invoked when the hub method with the specified method name is invoked. + /// + /// The first argument type. + /// The second argument type. + /// The third argument type. + /// The fourth argument type. + /// The fifth argument type. + /// The sixth argument type. + /// The hub connection. + /// The name of the hub method to define. + /// The handler that will be raised when the hub method is invoked. + /// A subscription that can be disposed to unsubscribe from the hub method. + public static IDisposable On(this HubConnection hubConnection, string methodName, Func handler) + { + if (hubConnection == null) + { + throw new ArgumentNullException(nameof(hubConnection)); + } + + return hubConnection.On(methodName, + new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6) }, + args => handler((T1)args[0], (T2)args[1], (T3)args[2], (T4)args[3], (T5)args[4], (T6)args[5])); + } + + /// + /// Registers a handler that will be invoked when the hub method with the specified method name is invoked. + /// + /// The first argument type. + /// The second argument type. + /// The third argument type. + /// The fourth argument type. + /// The fifth argument type. + /// The sixth argument type. + /// The seventh argument type. + /// The hub connection. + /// The name of the hub method to define. + /// The handler that will be raised when the hub method is invoked. + /// A subscription that can be disposed to unsubscribe from the hub method. + public static IDisposable On(this HubConnection hubConnection, string methodName, Func handler) + { + if (hubConnection == null) + { + throw new ArgumentNullException(nameof(hubConnection)); + } + + return hubConnection.On(methodName, + new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7) }, + args => handler((T1)args[0], (T2)args[1], (T3)args[2], (T4)args[3], (T5)args[4], (T6)args[5], (T7)args[6])); + } + + /// + /// Registers a handler that will be invoked when the hub method with the specified method name is invoked. + /// + /// The first argument type. + /// The second argument type. + /// The third argument type. + /// The fourth argument type. + /// The fifth argument type. + /// The sixth argument type. + /// The seventh argument type. + /// The eighth argument type. + /// The hub connection. + /// The name of the hub method to define. + /// The handler that will be raised when the hub method is invoked. + /// A subscription that can be disposed to unsubscribe from the hub method. + public static IDisposable On(this HubConnection hubConnection, string methodName, Func handler) + { + if (hubConnection == null) + { + throw new ArgumentNullException(nameof(hubConnection)); + } + + return hubConnection.On(methodName, + new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8) }, + args => handler((T1)args[0], (T2)args[1], (T3)args[2], (T4)args[3], (T5)args[4], (T6)args[5], (T7)args[6], (T8)args[7])); + } } } diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.Extensions.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.Extensions.cs index cf6fa8e64d..24e8eb88bf 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.Extensions.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.Extensions.cs @@ -20,13 +20,39 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests new object[0]); } + [Fact] + public async Task OnAsync() + { + await InvokeOn( + (hubConnection, tcs) => hubConnection.On("Foo", + () => + { + tcs.SetResult(new object[0]); + return Task.CompletedTask; + }), + new object[0]); + } + [Fact] public async Task OnT1() { await InvokeOn( (hubConnection, tcs) => hubConnection.On("Foo", - r => tcs.SetResult(new object[] {r})), - new object[] {42}); + r => tcs.SetResult(new object[] { r })), + new object[] { 42 }); + } + + [Fact] + public async Task OnT1Async() + { + await InvokeOn( + (hubConnection, tcs) => hubConnection.On("Foo", + r => + { + tcs.SetResult(new object[] { r }); + return Task.CompletedTask; + }), + new object[] { 42 }); } [Fact] @@ -34,8 +60,21 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests { await InvokeOn( (hubConnection, tcs) => hubConnection.On("Foo", - (r1, r2) => tcs.SetResult(new object[] {r1, r2})), - new object[] {42, "abc"}); + (r1, r2) => tcs.SetResult(new object[] { r1, r2 })), + new object[] { 42, "abc" }); + } + + [Fact] + public async Task OnT2Async() + { + await InvokeOn( + (hubConnection, tcs) => hubConnection.On("Foo", + (r1, r2) => + { + tcs.SetResult(new object[] { r1, r2 }); + return Task.CompletedTask; + }), + new object[] { 42, "abc" }); } [Fact] @@ -43,8 +82,21 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests { await InvokeOn( (hubConnection, tcs) => hubConnection.On("Foo", - (r1, r2, r3) => tcs.SetResult(new object[] {r1, r2, r3})), - new object[] {42, "abc", 24.0f}); + (r1, r2, r3) => tcs.SetResult(new object[] { r1, r2, r3 })), + new object[] { 42, "abc", 24.0f }); + } + + [Fact] + public async Task OnT3Async() + { + await InvokeOn( + (hubConnection, tcs) => hubConnection.On("Foo", + (r1, r2, r3) => + { + tcs.SetResult(new object[] { r1, r2, r3 }); + return Task.CompletedTask; + }), + new object[] { 42, "abc", 24.0f }); } [Fact] @@ -52,8 +104,21 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests { await InvokeOn( (hubConnection, tcs) => hubConnection.On("Foo", - (r1, r2, r3, r4) => tcs.SetResult(new object[] {r1, r2, r3, r4})), - new object[] {42, "abc", 24.0f, 10d}); + (r1, r2, r3, r4) => tcs.SetResult(new object[] { r1, r2, r3, r4 })), + new object[] { 42, "abc", 24.0f, 10d }); + } + + [Fact] + public async Task OnT4Async() + { + await InvokeOn( + (hubConnection, tcs) => hubConnection.On("Foo", + (r1, r2, r3, r4) => + { + tcs.SetResult(new object[] { r1, r2, r3, r4 }); + return Task.CompletedTask; + }), + new object[] { 42, "abc", 24.0f, 10d }); } [Fact] @@ -61,8 +126,21 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests { await InvokeOn( (hubConnection, tcs) => hubConnection.On("Foo", - (r1, r2, r3, r4, r5) => tcs.SetResult(new object[] {r1, r2, r3, r4, r5})), - new object[] {42, "abc", 24.0f, 10d, "123"}); + (r1, r2, r3, r4, r5) => tcs.SetResult(new object[] { r1, r2, r3, r4, r5 })), + new object[] { 42, "abc", 24.0f, 10d, "123" }); + } + + [Fact] + public async Task OnT5Async() + { + await InvokeOn( + (hubConnection, tcs) => hubConnection.On("Foo", + (r1, r2, r3, r4, r5) => + { + tcs.SetResult(new object[] { r1, r2, r3, r4, r5 }); + return Task.CompletedTask; + }), + new object[] { 42, "abc", 24.0f, 10d, "123" }); } [Fact] @@ -70,8 +148,21 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests { await InvokeOn( (hubConnection, tcs) => hubConnection.On("Foo", - (r1, r2, r3, r4, r5, r6) => tcs.SetResult(new object[] {r1, r2, r3, r4, r5, r6})), - new object[] {42, "abc", 24.0f, 10d, "123", 24}); + (r1, r2, r3, r4, r5, r6) => tcs.SetResult(new object[] { r1, r2, r3, r4, r5, r6 })), + new object[] { 42, "abc", 24.0f, 10d, "123", 24 }); + } + + [Fact] + public async Task OnT6Async() + { + await InvokeOn( + (hubConnection, tcs) => hubConnection.On("Foo", + (r1, r2, r3, r4, r5, r6) => + { + tcs.SetResult(new object[] { r1, r2, r3, r4, r5, r6 }); + return Task.CompletedTask; + }), + new object[] { 42, "abc", 24.0f, 10d, "123", 24 }); } [Fact] @@ -79,8 +170,21 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests { await InvokeOn( (hubConnection, tcs) => hubConnection.On("Foo", - (r1, r2, r3, r4, r5, r6, r7) => tcs.SetResult(new object[] {r1, r2, r3, r4, r5, r6, r7})), - new object[] {42, "abc", 24.0f, 10d, "123", 24, 'c'}); + (r1, r2, r3, r4, r5, r6, r7) => tcs.SetResult(new object[] { r1, r2, r3, r4, r5, r6, r7 })), + new object[] { 42, "abc", 24.0f, 10d, "123", 24, 'c' }); + } + + [Fact] + public async Task OnT7Async() + { + await InvokeOn( + (hubConnection, tcs) => hubConnection.On("Foo", + (r1, r2, r3, r4, r5, r6, r7) => + { + tcs.SetResult(new object[] { r1, r2, r3, r4, r5, r6, r7 }); + return Task.CompletedTask; + }), + new object[] { 42, "abc", 24.0f, 10d, "123", 24, 'c' }); } [Fact] @@ -88,8 +192,21 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests { await InvokeOn( (hubConnection, tcs) => hubConnection.On("Foo", - (r1, r2, r3, r4, r5, r6, r7, r8) => tcs.SetResult(new object[] {r1, r2, r3, r4, r5, r6, r7, r8})), - new object[] {42, "abc", 24.0f, 10d, "123", 24, 'c', "XYZ"}); + (r1, r2, r3, r4, r5, r6, r7, r8) => tcs.SetResult(new object[] { r1, r2, r3, r4, r5, r6, r7, r8 })), + new object[] { 42, "abc", 24.0f, 10d, "123", 24, 'c', "XYZ" }); + } + + [Fact] + public async Task OnT8Async() + { + await InvokeOn( + (hubConnection, tcs) => hubConnection.On("Foo", + (r1, r2, r3, r4, r5, r6, r7, r8) => + { + tcs.SetResult(new object[] { r1, r2, r3, r4, r5, r6, r7, r8 }); + return Task.CompletedTask; + }), + new object[] { 42, "abc", 24.0f, 10d, "123", 24, 'c', "XYZ" }); } private async Task InvokeOn(Action> onAction, object[] args) @@ -137,7 +254,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests invocationId = "1", type = 1, target = "Foo", - arguments = new object[] {42, "42"} + arguments = new object[] { 42, "42" } }).OrTimeout(); await connection.ReceiveJsonMessage( @@ -146,7 +263,49 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests invocationId = "2", type = 1, target = "Foo", - arguments = new object[] {42} + arguments = new object[] { 42 } + }).OrTimeout(); + + Assert.Equal(42, await receiveTcs.Task.OrTimeout()); + } + finally + { + await hubConnection.DisposeAsync().OrTimeout(); + } + } + + [Fact] + public async Task ConnectionNotClosedOnAsyncCallbackArgumentCountMismatch() + { + var connection = new TestConnection(); + var hubConnection = CreateHubConnection(connection); + var receiveTcs = new TaskCompletionSource(); + + try + { + hubConnection.On("Foo", r => + { + receiveTcs.SetResult(r); + return Task.CompletedTask; + }); + await hubConnection.StartAsync().OrTimeout(); + + await connection.ReceiveJsonMessage( + new + { + invocationId = "1", + type = 1, + target = "Foo", + arguments = new object[] { 42, "42" } + }).OrTimeout(); + + await connection.ReceiveJsonMessage( + new + { + invocationId = "2", + type = 1, + target = "Foo", + arguments = new object[] { 42 } }).OrTimeout(); Assert.Equal(42, await receiveTcs.Task.OrTimeout()); @@ -175,7 +334,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests invocationId = "1", type = 1, target = "Foo", - arguments = new object[] {"xxx"} + arguments = new object[] { "xxx" } }).OrTimeout(); await connection.ReceiveJsonMessage( @@ -184,7 +343,49 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests invocationId = "2", type = 1, target = "Foo", - arguments = new object[] {42} + arguments = new object[] { 42 } + }).OrTimeout(); + + Assert.Equal(42, await receiveTcs.Task.OrTimeout()); + } + finally + { + await hubConnection.DisposeAsync().OrTimeout(); + } + } + + [Fact] + public async Task ConnectionNotClosedOnAsyncCallbackArgumentTypeMismatch() + { + var connection = new TestConnection(); + var hubConnection = CreateHubConnection(connection); + var receiveTcs = new TaskCompletionSource(); + + try + { + hubConnection.On("Foo", r => + { + receiveTcs.SetResult(r); + return Task.CompletedTask; + }); + await hubConnection.StartAsync().OrTimeout(); + + await connection.ReceiveJsonMessage( + new + { + invocationId = "1", + type = 1, + target = "Foo", + arguments = new object[] { "xxx" } + }).OrTimeout(); + + await connection.ReceiveJsonMessage( + new + { + invocationId = "2", + type = 1, + target = "Foo", + arguments = new object[] { 42 } }).OrTimeout(); Assert.Equal(42, await receiveTcs.Task.OrTimeout());