Adding strongly typed Invoke and On overloads

See: #311
This commit is contained in:
Pawel Kadluczka 2017-05-18 14:10:20 -07:00 committed by Pawel Kadluczka
parent 8d5ce5f38b
commit 4bb9721d39
8 changed files with 351 additions and 30 deletions

View File

@ -47,11 +47,7 @@ namespace ClientSample
};
// Set up handler
connection.On("Send", new[] { typeof(string) }, a =>
{
var message = (string)a[0];
Console.WriteLine(message);
});
connection.On<string>("Send", Console.WriteLine);
while (!cts.Token.IsCancellationRequested)
{

View File

@ -109,18 +109,12 @@ namespace Microsoft.AspNetCore.SignalR.Client
}
// TODO: Client return values/tasks?
// TODO: Overloads for void hub methods
// TODO: Overloads that use type parameters (like On<T1>, On<T1, T2>, etc.)
public void On(string methodName, Type[] parameterTypes, Action<object[]> handler)
{
var invocationHandler = new InvocationHandler(parameterTypes, handler);
_handlers.AddOrUpdate(methodName, invocationHandler, (_, __) => invocationHandler);
}
public Task<T> Invoke<T>(string methodName, params object[] args) => Invoke<T>(methodName, CancellationToken.None, args);
public async Task<T> Invoke<T>(string methodName, CancellationToken cancellationToken, params object[] args) => ((T)(await Invoke(methodName, typeof(T), cancellationToken, args)));
public Task<object> Invoke(string methodName, Type returnType, params object[] args) => Invoke(methodName, returnType, CancellationToken.None, args);
public async Task<object> Invoke(string methodName, Type returnType, CancellationToken cancellationToken, params object[] args)
{
ThrowIfConnectionTerminated();

View File

@ -0,0 +1,145 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.SignalR.Client
{
public static class HubConnectionExtensions
{
public static Task Invoke(this HubConnection hubConnection, string methodName, params object[] args) =>
Invoke(hubConnection, methodName, CancellationToken.None, args);
public static Task Invoke(this HubConnection hubConnection, string methodName, CancellationToken cancellationToken, params object[] args)
{
if (hubConnection == null)
{
throw new ArgumentNullException(nameof(hubConnection));
}
return hubConnection.Invoke(methodName, typeof(object), cancellationToken, args);
}
public static Task<TResult> Invoke<TResult>(this HubConnection hubConnection, string methodName, params object[] args) =>
Invoke<TResult>(hubConnection, methodName, CancellationToken.None, args);
public async static Task<TResult> Invoke<TResult>(this HubConnection hubConnection, string methodName, CancellationToken cancellationToken, params object[] args)
{
if (hubConnection == null)
{
throw new ArgumentNullException(nameof(hubConnection));
}
return (TResult)await hubConnection.Invoke(methodName, typeof(TResult), cancellationToken, args);
}
public static void On(this HubConnection hubConnection, string methodName, Action handler)
{
if (hubConnection == null)
{
throw new ArgumentNullException(nameof(hubConnection));
}
hubConnection.On(methodName, Type.EmptyTypes, args => handler());
}
public static void On<T1>(this HubConnection hubConnection, string methodName, Action<T1> handler)
{
if (hubConnection == null)
{
throw new ArgumentNullException(nameof(hubConnection));
}
hubConnection.On(methodName,
new[] { typeof(T1) },
args => handler((T1)args[0]));
}
public static void On<T1, T2>(this HubConnection hubConnection, string methodName, Action<T1, T2> handler)
{
if (hubConnection == null)
{
throw new ArgumentNullException(nameof(hubConnection));
}
hubConnection.On(methodName,
new[] { typeof(T1), typeof(T2) },
args => handler((T1)args[0], (T2)args[1]));
}
public static void On<T1, T2, T3>(this HubConnection hubConnection, string methodName, Action<T1, T2, T3> handler)
{
if (hubConnection == null)
{
throw new ArgumentNullException(nameof(hubConnection));
}
hubConnection.On(methodName,
new[] { typeof(T1), typeof(T2), typeof(T3) },
args => handler((T1)args[0], (T2)args[1], (T3)args[2]));
}
public static void On<T1, T2, T3, T4>(this HubConnection hubConnection, string methodName, Action<T1, T2, T3, T4> handler)
{
if (hubConnection == null)
{
throw new ArgumentNullException(nameof(hubConnection));
}
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]));
}
public static void On<T1, T2, T3, T4, T5>(this HubConnection hubConnection, string methodName, Action<T1, T2, T3, T4, T5> handler)
{
if (hubConnection == null)
{
throw new ArgumentNullException(nameof(hubConnection));
}
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]));
}
public static void On<T1, T2, T3, T4, T5, T6>(this HubConnection hubConnection, string methodName, Action<T1, T2, T3, T4, T5, T6> handler)
{
if (hubConnection == null)
{
throw new ArgumentNullException(nameof(hubConnection));
}
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]));
}
public static void On<T1, T2, T3, T4, T5, T6, T7>(this HubConnection hubConnection, string methodName, Action<T1, T2, T3, T4, T5, T6, T7> handler)
{
if (hubConnection == null)
{
throw new ArgumentNullException(nameof(hubConnection));
}
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]));
}
public static void On<T1, T2, T3, T4, T5, T6, T7, T8>(this HubConnection hubConnection, string methodName, Action<T1, T2, T3, T4, T5, T6, T7, T8> handler)
{
if (hubConnection == null)
{
throw new ArgumentNullException(nameof(hubConnection));
}
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]));
}
}
}

View File

@ -128,12 +128,9 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
await connection.StartAsync(TransportType.LongPolling, httpClient);
var tcs = new TaskCompletionSource<string>();
connection.On("Echo", new[] { typeof(string) }, a =>
{
tcs.TrySetResult((string)a[0]);
});
connection.On<string>("Echo", tcs.SetResult);
await connection.Invoke<Task>("CallEcho", originalMessage).OrTimeout();
await connection.Invoke("CallEcho", originalMessage).OrTimeout();
Assert.Equal(originalMessage, await tcs.Task.OrTimeout());
}
@ -157,7 +154,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
await connection.StartAsync(TransportType.LongPolling, httpClient);
var ex = await Assert.ThrowsAnyAsync<Exception>(
async () => await connection.Invoke<object>("!@#$%"));
async () => await connection.Invoke("!@#$%"));
Assert.Equal("Unknown hub method '!@#$%'", ex.Message);
}

View File

@ -0,0 +1,186 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Microsoft.AspNetCore.SignalR.Tests.Common;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNetCore.SignalR.Client.Tests
{
public class HubConnectionExtensionsTests
{
[Fact]
public async Task On()
{
await InvokeOn(
(hubConnection, tcs) => hubConnection.On("Foo",
() => tcs.SetResult(new object[0])),
new object[0]);
}
[Fact]
public async Task OnT1()
{
await InvokeOn(
(hubConnection, tcs) => hubConnection.On<int>("Foo",
r => tcs.SetResult(new object[] { r })),
new object[] { 42 });
}
[Fact]
public async Task OnT2()
{
await InvokeOn(
(hubConnection, tcs) => hubConnection.On<int, string>("Foo",
(r1, r2) => tcs.SetResult(new object[] { r1, r2 })),
new object[] { 42, "abc" });
}
[Fact]
public async Task OnT3()
{
await InvokeOn(
(hubConnection, tcs) => hubConnection.On<int, string, float>("Foo",
(r1, r2, r3) => tcs.SetResult(new object[] { r1, r2, r3 })),
new object[] { 42, "abc", 24.0f });
}
[Fact]
public async Task OnT4()
{
await InvokeOn(
(hubConnection, tcs) => hubConnection.On<int, string, float, double>("Foo",
(r1, r2, r3, r4) => tcs.SetResult(new object[] { r1, r2, r3, r4 })),
new object[] { 42, "abc", 24.0f, 10d });
}
[Fact]
public async Task OnT5()
{
await InvokeOn(
(hubConnection, tcs) => hubConnection.On<int, string, float, double, string>("Foo",
(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 OnT6()
{
await InvokeOn(
(hubConnection, tcs) => hubConnection.On<int, string, float, double, string, byte>("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 });
}
[Fact]
public async Task OnT7()
{
await InvokeOn(
(hubConnection, tcs) => hubConnection.On<int, string, float, double, string, byte, char>("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' });
}
[Fact]
public async Task OnT8()
{
await InvokeOn(
(hubConnection, tcs) => hubConnection.On<int, string, float, double, string, byte, char, string>("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" });
}
private async Task InvokeOn(Action<HubConnection, TaskCompletionSource<object[]>> onAction, object[] args)
{
var connection = new TestConnection();
var hubConnection = new HubConnection(connection, new JsonHubProtocol(new JsonSerializer()), new LoggerFactory());
var handlerTcs = new TaskCompletionSource<object[]>();
try
{
onAction(hubConnection, handlerTcs);
await hubConnection.StartAsync();
await connection.ReceiveJsonMessage(
new
{
invocationId = "1",
type = 1,
target = "Foo",
arguments = args
}).OrTimeout();
var result = await handlerTcs.Task.OrTimeout();
}
finally
{
await hubConnection.DisposeAsync().OrTimeout();
await connection.DisposeAsync().OrTimeout();
}
}
[Fact]
public async Task ConnectionClosedOnCallbackArgumentCountMismatch()
{
var connection = new TestConnection();
var hubConnection = new HubConnection(connection, new JsonHubProtocol(new JsonSerializer()), new LoggerFactory());
var closeTcs = new TaskCompletionSource<Exception>();
hubConnection.Closed += e => closeTcs.TrySetException(e);
try
{
hubConnection.On<int>("Foo", r => { });
await hubConnection.StartAsync();
await connection.ReceiveJsonMessage(
new
{
invocationId = "1",
type = 1,
target = "Foo",
arguments = new object[] { 42, "42" }
}).OrTimeout();
var ex = await Assert.ThrowsAsync<FormatException>(async () => await closeTcs.Task.OrTimeout());
Assert.Equal("Invocation provides 2 argument(s) but target expects 1.", ex.Message);
}
finally
{
await hubConnection.DisposeAsync().OrTimeout();
await connection.DisposeAsync().OrTimeout();
}
}
[Fact]
public async Task ConnectionClosedOnCallbackArgumentTypeMismatch()
{
var connection = new TestConnection();
var hubConnection = new HubConnection(connection, new JsonHubProtocol(new JsonSerializer()), new LoggerFactory());
var closeTcs = new TaskCompletionSource<Exception>();
hubConnection.Closed += e => closeTcs.TrySetException(e);
try
{
hubConnection.On<int>("Foo", r => { });
await hubConnection.StartAsync();
await connection.ReceiveJsonMessage(
new
{
invocationId = "1",
type = 1,
target = "Foo",
arguments = new object[] { "xxx" }
}).OrTimeout();
var ex = await Assert.ThrowsAsync<FormatException>(async () => await closeTcs.Task.OrTimeout());
}
finally
{
await hubConnection.DisposeAsync().OrTimeout();
await connection.DisposeAsync().OrTimeout();
}
}
}
}

View File

@ -1,3 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
@ -22,7 +25,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
{
await hubConnection.StartAsync();
var invokeTask = hubConnection.Invoke("Foo", typeof(void));
var invokeTask = hubConnection.Invoke("Foo");
var invokeMessage = await connection.ReadSentTextMessageAsync().OrTimeout();
@ -44,7 +47,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
{
await hubConnection.StartAsync();
var invokeTask = hubConnection.Invoke("Foo", typeof(void));
var invokeTask = hubConnection.Invoke("Foo");
await connection.ReceiveJsonMessage(new { invocationId = "1", type = 3 }).OrTimeout();
@ -136,15 +139,12 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
{
await hubConnection.StartAsync();
hubConnection.On("Foo", new[] { typeof(int), typeof(string), typeof(float) }, (a) => handlerCalled.TrySetResult(a));
hubConnection.On<int, string, float>("Foo", (r1, r2, r3) => handlerCalled.TrySetResult(new object[] { r1, r2, r3 }));
await connection.ReceiveJsonMessage(new { invocationId = "1", type = 1, target = "Foo", arguments = new object[] { 1, "Foo", 2.0f } }).OrTimeout();
var args = new object[] { 1, "Foo", 2.0f };
await connection.ReceiveJsonMessage(new { invocationId = "1", type = 1, target = "Foo", arguments = args }).OrTimeout();
var results = await handlerCalled.Task.OrTimeout();
Assert.Equal(3, results.Length);
Assert.Equal(1, results[0]);
Assert.Equal("Foo", results[1]);
Assert.Equal(2.0f, results[2]);
Assert.Equal(args, await handlerCalled.Task.OrTimeout());
}
finally
{

View File

@ -199,7 +199,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
await hubConnection.StartAsync(new TestTransportFactory(Mock.Of<ITransport>()), httpClient: null);
await hubConnection.DisposeAsync();
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
async () => await hubConnection.Invoke("test", typeof(int)));
async () => await hubConnection.Invoke<int>("test"));
Assert.Equal("Connection has been terminated.", exception.Message);
}
@ -216,7 +216,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
var hubConnection = new HubConnection(mockConnection.Object, new LoggerFactory());
await hubConnection.StartAsync(new TestTransportFactory(Mock.Of<ITransport>()), httpClient: null);
var invokeTask = hubConnection.Invoke("testMethod", typeof(int));
var invokeTask = hubConnection.Invoke<int>("testMethod");
await hubConnection.DisposeAsync();
await Assert.ThrowsAsync<TaskCanceledException>(async () => await invokeTask);
@ -235,7 +235,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
var hubConnection = new HubConnection(mockConnection.Object, new LoggerFactory());
await hubConnection.StartAsync(new TestTransportFactory(Mock.Of<ITransport>()), httpClient: null);
var invokeTask = hubConnection.Invoke("testMethod", typeof(int));
var invokeTask = hubConnection.Invoke<int>("testMethod");
await hubConnection.DisposeAsync();
var thrown = await Assert.ThrowsAsync(exception.GetType(), async () => await invokeTask);

View File

@ -1,4 +1,7 @@
using System;
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net.Http;
using System.Text;
using System.Threading;