Merge pull request #11496 from NickDarvey/allow-derived-args-with-synthetic-args
Fixes dispatching of derived type arguments to hub methods
This commit is contained in:
commit
eb1aa2433b
|
|
@ -261,7 +261,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
|||
for (var parameterPointer = 0; parameterPointer < arguments.Length; parameterPointer++)
|
||||
{
|
||||
if (hubMethodInvocationMessage.Arguments.Length > hubInvocationArgumentPointer &&
|
||||
hubMethodInvocationMessage.Arguments[hubInvocationArgumentPointer].GetType() == descriptor.OriginalParameterTypes[parameterPointer])
|
||||
descriptor.OriginalParameterTypes[parameterPointer].IsAssignableFrom(hubMethodInvocationMessage.Arguments[hubInvocationArgumentPointer].GetType()))
|
||||
{
|
||||
// The types match so it isn't a synthetic argument, just copy it into the arguments array
|
||||
arguments[parameterPointer] = hubMethodInvocationMessage.Arguments[hubInvocationArgumentPointer];
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Tests
|
||||
{
|
||||
|
|
@ -666,6 +668,30 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return output.Reader;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<string> DerivedParameterInterfaceAsyncEnumerable(IDerivedParameterTestObject param)
|
||||
{
|
||||
await Task.Yield();
|
||||
yield return param.Value;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<string> DerivedParameterBaseClassAsyncEnumerable(DerivedParameterTestObjectBase param)
|
||||
{
|
||||
await Task.Yield();
|
||||
yield return param.Value;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<string> DerivedParameterInterfaceAsyncEnumerableWithCancellation(IDerivedParameterTestObject param, [EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
await Task.Yield();
|
||||
yield return param.Value;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<string> DerivedParameterBaseClassAsyncEnumerableWithCancellation(DerivedParameterTestObjectBase param, [EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
await Task.Yield();
|
||||
yield return param.Value;
|
||||
}
|
||||
|
||||
public class AsyncEnumerableImpl<T> : IAsyncEnumerable<T>
|
||||
{
|
||||
private readonly IAsyncEnumerable<T> _inner;
|
||||
|
|
@ -758,6 +784,37 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDerivedParameterTestObject
|
||||
{
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
public abstract class DerivedParameterTestObjectBase : IDerivedParameterTestObject
|
||||
{
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
public class DerivedParameterTestObject : DerivedParameterTestObjectBase { }
|
||||
|
||||
public class DerivedParameterKnownTypesBinder : ISerializationBinder
|
||||
{
|
||||
private static readonly IEnumerable<Type> _knownTypes = new List<Type>()
|
||||
{
|
||||
typeof(DerivedParameterTestObject)
|
||||
};
|
||||
|
||||
public static ISerializationBinder Instance { get; } = new DerivedParameterKnownTypesBinder();
|
||||
|
||||
public void BindToName(Type serializedType, out string assemblyName, out string typeName)
|
||||
{
|
||||
assemblyName = null;
|
||||
typeName = serializedType.Name;
|
||||
}
|
||||
|
||||
public Type BindToType(string assemblyName, string typeName) =>
|
||||
_knownTypes.Single(type => type.Name == typeName);
|
||||
}
|
||||
}
|
||||
|
||||
public class SimpleHub : Hub
|
||||
|
|
|
|||
|
|
@ -3641,6 +3641,63 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hub methods might be written by users in a way that accepts an interface or base class as a parameter
|
||||
/// and deserialization could supply a derived class.
|
||||
/// This test ensures implementation and subclass arguments are correctly bound for dispatch.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(nameof(StreamingHub.DerivedParameterInterfaceAsyncEnumerable))]
|
||||
[InlineData(nameof(StreamingHub.DerivedParameterBaseClassAsyncEnumerable))]
|
||||
[InlineData(nameof(StreamingHub.DerivedParameterInterfaceAsyncEnumerableWithCancellation))]
|
||||
[InlineData(nameof(StreamingHub.DerivedParameterBaseClassAsyncEnumerableWithCancellation))]
|
||||
public async Task CanPassDerivedParameterToStreamHubMethod(string method)
|
||||
{
|
||||
using (StartVerifiableLog())
|
||||
{
|
||||
var argument = new StreamingHub.DerivedParameterTestObject { Value = "test" };
|
||||
var protocolOptions = new NewtonsoftJsonHubProtocolOptions
|
||||
{
|
||||
PayloadSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
// The usage of TypeNameHandling.All is a security risk.
|
||||
// If you're implementing this in your own application instead use your own 'type' field and a custom JsonConverter
|
||||
// or ensure you're restricting to only known types with a custom SerializationBinder like we are here.
|
||||
// See https://github.com/aspnet/AspNetCore/issues/11495#issuecomment-505047422
|
||||
TypeNameHandling = TypeNameHandling.All,
|
||||
SerializationBinder = StreamingHub.DerivedParameterKnownTypesBinder.Instance
|
||||
}
|
||||
};
|
||||
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(
|
||||
services => services.AddSignalR()
|
||||
.AddNewtonsoftJsonProtocol(o => o.PayloadSerializerSettings = protocolOptions.PayloadSerializerSettings),
|
||||
LoggerFactory);
|
||||
var connectionHandler = serviceProvider.GetService<HubConnectionHandler<StreamingHub>>();
|
||||
var invocationBinder = new Mock<IInvocationBinder>();
|
||||
invocationBinder.Setup(b => b.GetStreamItemType(It.IsAny<string>())).Returns(typeof(string));
|
||||
|
||||
using (var client = new TestClient(
|
||||
protocol: new NewtonsoftJsonHubProtocol(Options.Create(protocolOptions)),
|
||||
invocationBinder: invocationBinder.Object))
|
||||
{
|
||||
var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
|
||||
|
||||
// Wait for a connection, or for the endpoint to fail.
|
||||
await client.Connected.OrThrowIfOtherFails(connectionHandlerTask).OrTimeout();
|
||||
|
||||
var messages = await client.StreamAsync(method, argument).OrTimeout();
|
||||
|
||||
Assert.Equal(2, messages.Count);
|
||||
HubConnectionHandlerTestUtils.AssertHubMessage(new StreamItemMessage(string.Empty, argument.Value), messages[0]);
|
||||
HubConnectionHandlerTestUtils.AssertHubMessage(CompletionMessage.Empty(string.Empty), messages[1]);
|
||||
|
||||
client.Dispose();
|
||||
|
||||
await connectionHandlerTask.OrTimeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomHubActivator<THub> : IHubActivator<THub> where THub : Hub
|
||||
{
|
||||
public int ReleaseCount;
|
||||
|
|
|
|||
Loading…
Reference in New Issue