Fixes dispatching of derived type arguments to hub methods with synthetic parameters
This commit is contained in:
parent
7f3e2acdac
commit
f80dbd3918
|
|
@ -261,7 +261,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
for (var parameterPointer = 0; parameterPointer < arguments.Length; parameterPointer++)
|
for (var parameterPointer = 0; parameterPointer < arguments.Length; parameterPointer++)
|
||||||
{
|
{
|
||||||
if (hubMethodInvocationMessage.Arguments.Length > hubInvocationArgumentPointer &&
|
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
|
// The types match so it isn't a synthetic argument, just copy it into the arguments array
|
||||||
arguments[parameterPointer] = hubMethodInvocationMessage.Arguments[hubInvocationArgumentPointer];
|
arguments[parameterPointer] = hubMethodInvocationMessage.Arguments[hubInvocationArgumentPointer];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.SignalR.Internal;
|
||||||
|
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SignalR.Tests.Internal
|
||||||
|
{
|
||||||
|
public class DefaultHubDispatcherTests
|
||||||
|
{
|
||||||
|
private class MockHubConnectionContext<TValue> : HubConnectionContext
|
||||||
|
{
|
||||||
|
public TaskCompletionSource<object> ReceivedCompleted = new TaskCompletionSource<object>();
|
||||||
|
public List<TValue> Values = new List<TValue>();
|
||||||
|
|
||||||
|
public MockHubConnectionContext(ConnectionContext connectionContext, HubConnectionContextOptions contextOptions, ILoggerFactory loggerFactory)
|
||||||
|
: base(connectionContext, contextOptions, loggerFactory) { }
|
||||||
|
|
||||||
|
public override ValueTask WriteAsync(HubMessage message, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (message is StreamItemMessage streamItemMessage)
|
||||||
|
Values.Add((TValue)streamItemMessage.Item);
|
||||||
|
|
||||||
|
else if (message is CompletionMessage completionMessage)
|
||||||
|
{
|
||||||
|
ReceivedCompleted.TrySetResult(null);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(completionMessage.Error))
|
||||||
|
{
|
||||||
|
throw new Exception("Error invoking hub method: " + completionMessage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else throw new NotImplementedException();
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DefaultHubDispatcher<THub> CreateDispatcher<THub>() where THub : Hub
|
||||||
|
{
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
serviceCollection.TryAddScoped(typeof(IHubActivator<>), typeof(DefaultHubActivator<>));
|
||||||
|
var provider = serviceCollection.BuildServiceProvider();
|
||||||
|
var serviceScopeFactory = provider.GetService<IServiceScopeFactory>();
|
||||||
|
|
||||||
|
return new DefaultHubDispatcher<THub>(
|
||||||
|
serviceScopeFactory,
|
||||||
|
new HubContext<THub>(new DefaultHubLifetimeManager<THub>(NullLogger<DefaultHubLifetimeManager<THub>>.Instance)),
|
||||||
|
Options.Create(new HubOptions<THub>()),
|
||||||
|
Options.Create(new HubOptions()),
|
||||||
|
new Logger<DefaultHubDispatcher<THub>>(NullLoggerFactory.Instance));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MockHubConnectionContext<TValue> CreateConnectionContext<TValue>()
|
||||||
|
{
|
||||||
|
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
||||||
|
var connection = new DefaultConnectionContext(Guid.NewGuid().ToString(), pair.Application, pair.Transport);
|
||||||
|
var contextOptions = new HubConnectionContextOptions() { KeepAliveInterval = TimeSpan.Zero };
|
||||||
|
|
||||||
|
return new MockHubConnectionContext<TValue>(
|
||||||
|
connection,
|
||||||
|
contextOptions,
|
||||||
|
NullLoggerFactory.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For <see cref="DispatchesDerivedArguments"/>.
|
||||||
|
/// </summary>
|
||||||
|
private interface ITestDerivedParameter
|
||||||
|
{
|
||||||
|
public string Value { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For <see cref="DispatchesDerivedArguments"/>.
|
||||||
|
/// </summary>
|
||||||
|
private abstract class TestDerivedParameterBase
|
||||||
|
{
|
||||||
|
public TestDerivedParameterBase(string value) => Value = value;
|
||||||
|
public string Value { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For <see cref="DispatchesDerivedArguments"/>.
|
||||||
|
/// </summary>
|
||||||
|
private class TestDerivedParameter : TestDerivedParameterBase, ITestDerivedParameter
|
||||||
|
{
|
||||||
|
public TestDerivedParameter(string value) : base(value) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For <see cref="DispatchesDerivedArguments"/>.
|
||||||
|
/// </summary>
|
||||||
|
private class TestDerivedParameterHub : Hub
|
||||||
|
{
|
||||||
|
public async IAsyncEnumerable<string> TestSubclass(TestDerivedParameterBase param, [EnumeratorCancellation]CancellationToken token)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
yield return param.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<string> TestImplementation(ITestDerivedParameter param, [EnumeratorCancellation]CancellationToken token)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
yield return param.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 (e.g. Json.NET's TypeNameHandling = TypeNameHandling.All).
|
||||||
|
/// This test ensures implementation and subclass arguments are correctly bound for dispatch.
|
||||||
|
/// </summary>
|
||||||
|
[Theory]
|
||||||
|
[InlineData(nameof(TestDerivedParameterHub.TestImplementation))]
|
||||||
|
[InlineData(nameof(TestDerivedParameterHub.TestSubclass))]
|
||||||
|
public async Task DispatchesDerivedArguments(string methodName)
|
||||||
|
{
|
||||||
|
var message = new TestDerivedParameter("Yup");
|
||||||
|
var connectionContext = CreateConnectionContext<string>();
|
||||||
|
var dispatcher = CreateDispatcher<TestDerivedParameterHub>();
|
||||||
|
|
||||||
|
await dispatcher.DispatchMessageAsync(connectionContext, new StreamInvocationMessage("123", methodName, new[] { message }));
|
||||||
|
await (connectionContext as MockHubConnectionContext<string>).ReceivedCompleted.Task;
|
||||||
|
|
||||||
|
Assert.Single(connectionContext.Values, message.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue