From 2d6077db4a652b1b646399ad0e0378d915f61c04 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 16 Mar 2018 14:05:22 +1300 Subject: [PATCH] Removed params from proxy's SendAsync and rename to SendCoreAsync (#1605) --- samples/ChatSample/HubWithPresence.cs | 2 +- samples/ChatSample/Hubs/Chat.cs | 4 +- .../DefaultHubLifetimeManager.cs | 2 +- .../DynamicClientProxy.cs | 2 +- .../HubClients.cs | 2 +- .../HubClients`T.cs | 2 +- .../HubLifetimeManager.cs | 2 +- .../IClientProxy.cs | 10 +- .../IClientProxyExtensions.cs | 22 +- .../Proxies.cs | 44 ++-- .../TypedClientBuilder.cs | 8 +- .../RedisHubLifetimeManager.cs | 2 +- .../ClientProxyTests.cs | 207 ++++++++++++++++++ 13 files changed, 260 insertions(+), 49 deletions(-) create mode 100644 test/Microsoft.AspNetCore.SignalR.Tests/ClientProxyTests.cs diff --git a/samples/ChatSample/HubWithPresence.cs b/samples/ChatSample/HubWithPresence.cs index e54d8911d7..0e19c1f983 100644 --- a/samples/ChatSample/HubWithPresence.cs +++ b/samples/ChatSample/HubWithPresence.cs @@ -9,7 +9,7 @@ namespace ChatSample { public class HubWithPresence : Hub { - private IUserTracker _userTracker; + private readonly IUserTracker _userTracker; public HubWithPresence(IUserTracker userTracker) { diff --git a/samples/ChatSample/Hubs/Chat.cs b/samples/ChatSample/Hubs/Chat.cs index 75c1440992..0a1c33f39b 100644 --- a/samples/ChatSample/Hubs/Chat.cs +++ b/samples/ChatSample/Hubs/Chat.cs @@ -23,12 +23,12 @@ namespace ChatSample.Hubs public override Task OnUsersJoined(UserDetails[] users) { - return Clients.Client(Context.ConnectionId).SendAsync("UsersJoined", new[] { users }); + return Clients.Client(Context.ConnectionId).SendAsync("UsersJoined", users); } public override Task OnUsersLeft(UserDetails[] users) { - return Clients.Client(Context.ConnectionId).SendAsync("UsersLeft", new[] { users }); + return Clients.Client(Context.ConnectionId).SendAsync("UsersLeft", users); } public async Task Send(string message) diff --git a/src/Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs b/src/Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs index 5c8b1638dd..67cf01085f 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.SignalR.Internal.Protocol; namespace Microsoft.AspNetCore.SignalR { - public class DefaultHubLifetimeManager : HubLifetimeManager + public class DefaultHubLifetimeManager : HubLifetimeManager where THub : Hub { private readonly HubConnectionList _connections = new HubConnectionList(); private readonly HubGroupList _groups = new HubGroupList(); diff --git a/src/Microsoft.AspNetCore.SignalR.Core/DynamicClientProxy.cs b/src/Microsoft.AspNetCore.SignalR.Core/DynamicClientProxy.cs index 3a1d07e4bf..df3060f69a 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/DynamicClientProxy.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/DynamicClientProxy.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.SignalR public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { - result = _clientProxy.SendAsync(binder.Name, args); + result = _clientProxy.SendCoreAsync(binder.Name, args); return true; } } diff --git a/src/Microsoft.AspNetCore.SignalR.Core/HubClients.cs b/src/Microsoft.AspNetCore.SignalR.Core/HubClients.cs index d24d75d925..3944fbcb3a 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/HubClients.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/HubClients.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; namespace Microsoft.AspNetCore.SignalR { - public class HubClients : IHubClients + public class HubClients : IHubClients where THub : Hub { private readonly HubLifetimeManager _lifetimeManager; diff --git a/src/Microsoft.AspNetCore.SignalR.Core/HubClients`T.cs b/src/Microsoft.AspNetCore.SignalR.Core/HubClients`T.cs index 641b7d0675..bc6ad53675 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/HubClients`T.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/HubClients`T.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; namespace Microsoft.AspNetCore.SignalR { - public class HubClients : IHubClients + public class HubClients : IHubClients where THub : Hub { private readonly HubLifetimeManager _lifetimeManager; diff --git a/src/Microsoft.AspNetCore.SignalR.Core/HubLifetimeManager.cs b/src/Microsoft.AspNetCore.SignalR.Core/HubLifetimeManager.cs index f76074f392..e22080d346 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/HubLifetimeManager.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/HubLifetimeManager.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Microsoft.AspNetCore.SignalR { - public abstract class HubLifetimeManager + public abstract class HubLifetimeManager where THub : Hub { public abstract Task OnConnectedAsync(HubConnectionContext connection); diff --git a/src/Microsoft.AspNetCore.SignalR.Core/IClientProxy.cs b/src/Microsoft.AspNetCore.SignalR.Core/IClientProxy.cs index c3d6535ee9..8d72e0dbea 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/IClientProxy.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/IClientProxy.cs @@ -7,13 +7,17 @@ namespace Microsoft.AspNetCore.SignalR { public interface IClientProxy { + // client proxy method is called SendCoreAsync instead of SendAsync so that arrays of references + // like string[], e.g. SendAsync(string, string[]), do not choose SendAsync(string, object[]) + // over SendAsync(string, object) overload + /// /// Invokes a method on the connection(s) represented by the instance. /// Does not wait for a response from the receiver. /// - /// name of the method to invoke - /// argumetns to pass to the client + /// Name of the method to invoke. + /// A collection of arguments to pass to the client. /// A task that represents when the data has been sent to the client. - Task SendAsync(string method, object[] args); + Task SendCoreAsync(string method, object[] args); } } diff --git a/src/Microsoft.AspNetCore.SignalR.Core/IClientProxyExtensions.cs b/src/Microsoft.AspNetCore.SignalR.Core/IClientProxyExtensions.cs index acd37c5fe3..7bb83654ef 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/IClientProxyExtensions.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/IClientProxyExtensions.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.SignalR /// A task that represents when the data has been sent to the client. public static Task SendAsync(this IClientProxy clientProxy, string method) { - return clientProxy.SendAsync(method, Array.Empty()); + return clientProxy.SendCoreAsync(method, Array.Empty()); } /// @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.SignalR /// A task that represents when the data has been sent to the client. public static Task SendAsync(this IClientProxy clientProxy, string method, object arg1) { - return clientProxy.SendAsync(method, new object[] { arg1 }); + return clientProxy.SendCoreAsync(method, new object[] { arg1 }); } /// @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.SignalR /// A task that represents when the data has been sent to the client. public static Task SendAsync(this IClientProxy clientProxy, string method, object arg1, object arg2) { - return clientProxy.SendAsync(method, new object[] { arg1, arg2 }); + return clientProxy.SendCoreAsync(method, new object[] { arg1, arg2 }); } /// @@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.SignalR /// A task that represents when the data has been sent to the client. public static Task SendAsync(this IClientProxy clientProxy, string method, object arg1, object arg2, object arg3) { - return clientProxy.SendAsync(method, new object[] { arg1, arg2, arg3 }); + return clientProxy.SendCoreAsync(method, new object[] { arg1, arg2, arg3 }); } /// @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.SignalR /// A task that represents when the data has been sent to the client. public static Task SendAsync(this IClientProxy clientProxy, string method, object arg1, object arg2, object arg3, object arg4) { - return clientProxy.SendAsync(method, new object[] { arg1, arg2, arg3, arg4 }); + return clientProxy.SendCoreAsync(method, new object[] { arg1, arg2, arg3, arg4 }); } /// @@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.SignalR /// A task that represents when the data has been sent to the client. public static Task SendAsync(this IClientProxy clientProxy, string method, object arg1, object arg2, object arg3, object arg4, object arg5) { - return clientProxy.SendAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5 }); + return clientProxy.SendCoreAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5 }); } /// @@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.SignalR /// A task that represents when the data has been sent to the client. public static Task SendAsync(this IClientProxy clientProxy, string method, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6) { - return clientProxy.SendAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6 }); + return clientProxy.SendCoreAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6 }); } /// @@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.SignalR /// A task that represents when the data has been sent to the client. public static Task SendAsync(this IClientProxy clientProxy, string method, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6, object arg7) { - return clientProxy.SendAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7 }); + return clientProxy.SendCoreAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7 }); } /// @@ -148,7 +148,7 @@ namespace Microsoft.AspNetCore.SignalR /// A task that represents when the data has been sent to the client. public static Task SendAsync(this IClientProxy clientProxy, string method, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6, object arg7, object arg8) { - return clientProxy.SendAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 }); + return clientProxy.SendCoreAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 }); } /// @@ -169,7 +169,7 @@ namespace Microsoft.AspNetCore.SignalR /// A task that represents when the data has been sent to the client. public static Task SendAsync(this IClientProxy clientProxy, string method, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6, object arg7, object arg8, object arg9) { - return clientProxy.SendAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 }); + return clientProxy.SendCoreAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 }); } /// @@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.SignalR /// A task that represents when the data has been sent to the client. public static Task SendAsync(this IClientProxy clientProxy, string method, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6, object arg7, object arg8, object arg9, object arg10) { - return clientProxy.SendAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 }); + return clientProxy.SendCoreAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 }); } } } diff --git a/src/Microsoft.AspNetCore.SignalR.Core/Proxies.cs b/src/Microsoft.AspNetCore.SignalR.Core/Proxies.cs index ae7a89902a..a3283e067d 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/Proxies.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/Proxies.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Microsoft.AspNetCore.SignalR { - public class UserProxy : IClientProxy + public class UserProxy : IClientProxy where THub : Hub { private readonly string _userId; private readonly HubLifetimeManager _lifetimeManager; @@ -17,13 +17,13 @@ namespace Microsoft.AspNetCore.SignalR _userId = userId; } - public Task SendAsync(string method, params object[] args) + public Task SendCoreAsync(string method, object[] args) { return _lifetimeManager.SendUserAsync(_userId, method, args); } } - public class MultipleUserProxy : IClientProxy + public class MultipleUserProxy : IClientProxy where THub : Hub { private readonly IReadOnlyList _userIds; private readonly HubLifetimeManager _lifetimeManager; @@ -34,13 +34,13 @@ namespace Microsoft.AspNetCore.SignalR _userIds = userIds; } - public Task SendAsync(string method, params object[] args) + public Task SendCoreAsync(string method, object[] args) { return _lifetimeManager.SendUsersAsync(_userIds, method, args); } } - public class GroupProxy : IClientProxy + public class GroupProxy : IClientProxy where THub : Hub { private readonly string _groupName; private readonly HubLifetimeManager _lifetimeManager; @@ -51,16 +51,16 @@ namespace Microsoft.AspNetCore.SignalR _groupName = groupName; } - public Task SendAsync(string method, params object[] args) + public Task SendCoreAsync(string method, object[] args) { return _lifetimeManager.SendGroupAsync(_groupName, method, args); } } - public class MultipleGroupProxy : IClientProxy + public class MultipleGroupProxy : IClientProxy where THub : Hub { private readonly HubLifetimeManager _lifetimeManager; - private IReadOnlyList _groupNames; + private readonly IReadOnlyList _groupNames; public MultipleGroupProxy(HubLifetimeManager lifetimeManager, IReadOnlyList groupNames) { @@ -68,13 +68,13 @@ namespace Microsoft.AspNetCore.SignalR _groupNames = groupNames; } - public Task SendAsync(string method, params object[] args) + public Task SendCoreAsync(string method, object[] args) { return _lifetimeManager.SendGroupsAsync(_groupNames, method, args); } } - public class GroupExceptProxy : IClientProxy + public class GroupExceptProxy : IClientProxy where THub : Hub { private readonly string _groupName; private readonly HubLifetimeManager _lifetimeManager; @@ -87,13 +87,13 @@ namespace Microsoft.AspNetCore.SignalR _excludedIds = excludedIds; } - public Task SendAsync(string method, params object[] args) + public Task SendCoreAsync(string method, object[] args) { return _lifetimeManager.SendGroupExceptAsync(_groupName, method, args, _excludedIds); } } - public class AllClientProxy : IClientProxy + public class AllClientProxy : IClientProxy where THub : Hub { private readonly HubLifetimeManager _lifetimeManager; @@ -102,16 +102,16 @@ namespace Microsoft.AspNetCore.SignalR _lifetimeManager = lifetimeManager; } - public Task SendAsync(string method, params object[] args) + public Task SendCoreAsync(string method, object[] args) { return _lifetimeManager.SendAllAsync(method, args); } } - public class AllClientsExceptProxy : IClientProxy + public class AllClientsExceptProxy : IClientProxy where THub : Hub { private readonly HubLifetimeManager _lifetimeManager; - private IReadOnlyList _excludedIds; + private readonly IReadOnlyList _excludedIds; public AllClientsExceptProxy(HubLifetimeManager lifetimeManager, IReadOnlyList excludedIds) { @@ -119,13 +119,13 @@ namespace Microsoft.AspNetCore.SignalR _excludedIds = excludedIds; } - public Task SendAsync(string method, params object[] args) + public Task SendCoreAsync(string method, object[] args) { return _lifetimeManager.SendAllExceptAsync(method, args, _excludedIds); } } - public class SingleClientProxy : IClientProxy + public class SingleClientProxy : IClientProxy where THub : Hub { private readonly string _connectionId; private readonly HubLifetimeManager _lifetimeManager; @@ -136,16 +136,16 @@ namespace Microsoft.AspNetCore.SignalR _connectionId = connectionId; } - public Task SendAsync(string method, params object[] args) + public Task SendCoreAsync(string method, object[] args) { return _lifetimeManager.SendConnectionAsync(_connectionId, method, args); } } - public class MultipleClientProxy : IClientProxy + public class MultipleClientProxy : IClientProxy where THub : Hub { private readonly HubLifetimeManager _lifetimeManager; - private IReadOnlyList _connectionIds; + private readonly IReadOnlyList _connectionIds; public MultipleClientProxy(HubLifetimeManager lifetimeManager, IReadOnlyList connectionIds) { @@ -153,13 +153,13 @@ namespace Microsoft.AspNetCore.SignalR _connectionIds = connectionIds; } - public Task SendAsync(string method, params object[] args) + public Task SendCoreAsync(string method, object[] args) { return _lifetimeManager.SendConnectionsAsync(_connectionIds, method, args); } } - public class GroupManager : IGroupManager + public class GroupManager : IGroupManager where THub : Hub { private readonly HubLifetimeManager _lifetimeManager; diff --git a/src/Microsoft.AspNetCore.SignalR.Core/TypedClientBuilder.cs b/src/Microsoft.AspNetCore.SignalR.Core/TypedClientBuilder.cs index 4ad095284b..2513586d53 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/TypedClientBuilder.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/TypedClientBuilder.cs @@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.SignalR var methodBuilder = type.DefineMethod(interfaceMethodInfo.Name, methodAttributes); var invokeMethod = typeof(IClientProxy).GetMethod( - "SendAsync", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, + nameof(IClientProxy.SendCoreAsync), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(string), typeof(object[]) }, null); methodBuilder.SetReturnType(interfaceMethodInfo.ReturnType); @@ -132,14 +132,14 @@ namespace Microsoft.AspNetCore.SignalR var generator = methodBuilder.GetILGenerator(); - // Declare local variable to store the arguments to IClientProxy.SendAsync + // Declare local variable to store the arguments to IClientProxy.SendCoreAsync generator.DeclareLocal(typeof(object[])); // Get IClientProxy generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldfld, proxyField); - // The first argument to IClientProxy.SendAsync is this method's name + // The first argument to IClientProxy.SendCoreAsync is this method's name generator.Emit(OpCodes.Ldstr, interfaceMethodInfo.Name); // Create an new object array to hold all the parameters to this method @@ -157,7 +157,7 @@ namespace Microsoft.AspNetCore.SignalR generator.Emit(OpCodes.Stelem_Ref); } - // Call SendAsync + // Call SendCoreAsync generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Callvirt, invokeMethod); diff --git a/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs b/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs index 3d52960770..13d0d4d819 100644 --- a/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs +++ b/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs @@ -18,7 +18,7 @@ using StackExchange.Redis; namespace Microsoft.AspNetCore.SignalR.Redis { - public class RedisHubLifetimeManager : HubLifetimeManager, IDisposable + public class RedisHubLifetimeManager : HubLifetimeManager, IDisposable where THub : Hub { private readonly HubConnectionList _connections = new HubConnectionList(); // TODO: Investigate "memory leak" entries never get removed diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/ClientProxyTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/ClientProxyTests.cs new file mode 100644 index 0000000000..d3fd189d6b --- /dev/null +++ b/test/Microsoft.AspNetCore.SignalR.Tests/ClientProxyTests.cs @@ -0,0 +1,207 @@ +// 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.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.SignalR.Tests +{ + public class ClientHubProxyTests + { + public class FakeHub : Hub + { + } + + [Fact] + public async Task UserProxy_SendAsync_ArrayArgumentNotExpanded() + { + object[] resultArgs = null; + + var o = new Mock>(); + o.Setup(m => m.SendUserAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((userId, methodName, args) => { resultArgs = args; }) + .Returns(Task.CompletedTask); + + var proxy = new UserProxy(o.Object, string.Empty); + + var data = Encoding.UTF8.GetBytes("Hello world"); + await proxy.SendAsync("Method", data); + + Assert.NotNull(resultArgs); + var arg = (byte[]) Assert.Single(resultArgs); + + Assert.Same(data, arg); + } + + [Fact] + public async Task MultipleUserProxy_SendAsync_ArrayArgumentNotExpanded() + { + object[] resultArgs = null; + + var o = new Mock>(); + o.Setup(m => m.SendUsersAsync(It.IsAny>(), It.IsAny(), It.IsAny())) + .Callback, string, object[]>((userIds, methodName, args) => { resultArgs = args; }) + .Returns(Task.CompletedTask); + + var proxy = new MultipleUserProxy(o.Object, new List()); + + var data = Encoding.UTF8.GetBytes("Hello world"); + await proxy.SendAsync("Method", data); + + Assert.NotNull(resultArgs); + var arg = (byte[])Assert.Single(resultArgs); + + Assert.Same(data, arg); + } + + [Fact] + public async Task GroupProxy_SendAsync_ArrayArgumentNotExpanded() + { + object[] resultArgs = null; + + var o = new Mock>(); + o.Setup(m => m.SendGroupAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((groupName, methodName, args) => { resultArgs = args; }) + .Returns(Task.CompletedTask); + + var proxy = new GroupProxy(o.Object, string.Empty); + + var data = Encoding.UTF8.GetBytes("Hello world"); + await proxy.SendAsync("Method", data); + + Assert.NotNull(resultArgs); + var arg = (byte[])Assert.Single(resultArgs); + + Assert.Same(data, arg); + } + + [Fact] + public async Task MultipleGroupProxy_SendAsync_ArrayArgumentNotExpanded() + { + object[] resultArgs = null; + + var o = new Mock>(); + o.Setup(m => m.SendGroupsAsync(It.IsAny>(), It.IsAny(), It.IsAny())) + .Callback, string, object[]>((groupNames, methodName, args) => { resultArgs = args; }) + .Returns(Task.CompletedTask); + + var proxy = new MultipleGroupProxy(o.Object, new List()); + + var data = Encoding.UTF8.GetBytes("Hello world"); + await proxy.SendAsync("Method", data); + + Assert.NotNull(resultArgs); + var arg = (byte[])Assert.Single(resultArgs); + + Assert.Same(data, arg); + } + + [Fact] + public async Task GroupExceptProxy_SendAsync_ArrayArgumentNotExpanded() + { + object[] resultArgs = null; + + var o = new Mock>(); + o.Setup(m => m.SendGroupExceptAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .Callback>((groupName, methodName, args, excludedIds) => { resultArgs = args; }) + .Returns(Task.CompletedTask); + + var proxy = new GroupExceptProxy(o.Object, string.Empty, new List()); + + var data = Encoding.UTF8.GetBytes("Hello world"); + await proxy.SendAsync("Method", data); + + Assert.NotNull(resultArgs); + var arg = (byte[])Assert.Single(resultArgs); + + Assert.Same(data, arg); + } + + [Fact] + public async Task AllClientProxy_SendAsync_ArrayArgumentNotExpanded() + { + object[] resultArgs = null; + + var o = new Mock>(); + o.Setup(m => m.SendAllAsync(It.IsAny(), It.IsAny())) + .Callback((methodName, args) => { resultArgs = args; }) + .Returns(Task.CompletedTask); + + var proxy = new AllClientProxy(o.Object); + + var data = Encoding.UTF8.GetBytes("Hello world"); + await proxy.SendAsync("Method", data); + + Assert.NotNull(resultArgs); + var arg = (byte[])Assert.Single(resultArgs); + + Assert.Same(data, arg); + } + + [Fact] + public async Task AllClientsExceptProxy_SendAsync_ArrayArgumentNotExpanded() + { + object[] resultArgs = null; + + var o = new Mock>(); + o.Setup(m => m.SendAllExceptAsync(It.IsAny(), It.IsAny(), It.IsAny>())) + .Callback>((methodName, args, excludedIds) => { resultArgs = args; }) + .Returns(Task.CompletedTask); + + var proxy = new AllClientsExceptProxy(o.Object, new List()); + + var data = Encoding.UTF8.GetBytes("Hello world"); + await proxy.SendAsync("Method", data); + + Assert.NotNull(resultArgs); + var arg = (byte[])Assert.Single(resultArgs); + + Assert.Same(data, arg); + } + + [Fact] + public async Task SingleClientProxy_SendAsync_ArrayArgumentNotExpanded() + { + object[] resultArgs = null; + + var o = new Mock>(); + o.Setup(m => m.SendConnectionAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((connectionId, methodName, args) => { resultArgs = args; }) + .Returns(Task.CompletedTask); + + var proxy = new SingleClientProxy(o.Object, string.Empty); + + var data = Encoding.UTF8.GetBytes("Hello world"); + await proxy.SendAsync("Method", data); + + Assert.NotNull(resultArgs); + var arg = (byte[])Assert.Single(resultArgs); + + Assert.Same(data, arg); + } + + [Fact] + public async Task MultipleClientProxy_SendAsync_ArrayArgumentNotExpanded() + { + object[] resultArgs = null; + + var o = new Mock>(); + o.Setup(m => m.SendConnectionsAsync(It.IsAny>(), It.IsAny(), It.IsAny())) + .Callback, string, object[]>((connectionIds, methodName, args) => { resultArgs = args; }) + .Returns(Task.CompletedTask); + + var proxy = new MultipleClientProxy(o.Object, new List()); + + var data = Encoding.UTF8.GetBytes("Hello world"); + await proxy.SendAsync("Method", data); + + Assert.NotNull(resultArgs); + var arg = (byte[])Assert.Single(resultArgs); + + Assert.Same(data, arg); + } + } +} \ No newline at end of file