System.Text.Json Hub Protocol (#8932)
This commit is contained in:
parent
28970a3e93
commit
9fae14a926
|
|
@ -119,6 +119,7 @@
|
|||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Http.Connections.Client" ProjectPath="$(RepositoryRoot)src\SignalR\clients\csharp\Http.Connections.Client\src\Microsoft.AspNetCore.Http.Connections.Client.csproj" RefProjectPath="$(RepositoryRoot)src\SignalR\clients\csharp\Http.Connections.Client\ref\Microsoft.AspNetCore.Http.Connections.Client.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Http.Connections.Common" ProjectPath="$(RepositoryRoot)src\SignalR\common\Http.Connections.Common\src\Microsoft.AspNetCore.Http.Connections.Common.csproj" RefProjectPath="$(RepositoryRoot)src\SignalR\common\Http.Connections.Common\ref\Microsoft.AspNetCore.Http.Connections.Common.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Http.Connections" ProjectPath="$(RepositoryRoot)src\SignalR\common\Http.Connections\src\Microsoft.AspNetCore.Http.Connections.csproj" RefProjectPath="$(RepositoryRoot)src\SignalR\common\Http.Connections\ref\Microsoft.AspNetCore.Http.Connections.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Protocols.Json" ProjectPath="$(RepositoryRoot)src\SignalR\common\Protocols.Json\src\Microsoft.AspNetCore.SignalR.Protocols.Json.csproj" RefProjectPath="$(RepositoryRoot)src\SignalR\common\Protocols.Json\ref\Microsoft.AspNetCore.SignalR.Protocols.Json.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" ProjectPath="$(RepositoryRoot)src\SignalR\common\Protocols.MessagePack\src\Microsoft.AspNetCore.SignalR.Protocols.MessagePack.csproj" RefProjectPath="$(RepositoryRoot)src\SignalR\common\Protocols.MessagePack\ref\Microsoft.AspNetCore.SignalR.Protocols.MessagePack.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" ProjectPath="$(RepositoryRoot)src\SignalR\common\Protocols.NewtonsoftJson\src\Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj" RefProjectPath="$(RepositoryRoot)src\SignalR\common\Protocols.NewtonsoftJson\ref\Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Common" ProjectPath="$(RepositoryRoot)src\SignalR\common\SignalR.Common\src\Microsoft.AspNetCore.SignalR.Common.csproj" RefProjectPath="$(RepositoryRoot)src\SignalR\common\SignalR.Common\ref\Microsoft.AspNetCore.SignalR.Common.csproj" />
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
<AspNetCoreAppReferenceAndPackage Include="Microsoft.AspNetCore.Http.Features" />
|
||||
<AspNetCoreAppReferenceAndPackage Include="Microsoft.AspNetCore.Connections.Abstractions" />
|
||||
<AspNetCoreAppReferenceAndPackage Include="Microsoft.AspNetCore.Http.Connections.Common" />
|
||||
<AspNetCoreAppReferenceAndPackage Include="Microsoft.AspNetCore.SignalR.Protocols.Json" />
|
||||
<AspNetCoreAppReferenceAndPackage Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" />
|
||||
<AspNetCoreAppReferenceAndPackage Include="Microsoft.AspNetCore.SignalR.Common" />
|
||||
<AspNetCoreAppReferenceAndPackage Include="Microsoft.AspNetCore.Components.Browser" />
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<Reference Include="Microsoft.AspNetCore.Mvc.Components.Prerendering" />
|
||||
<Reference Include="Microsoft.AspNetCore.Components.Server" />
|
||||
<Reference Include="Microsoft.AspNetCore.Mvc" />
|
||||
<Reference Include="Newtonsoft.Json" />
|
||||
<ProjectReference Include="..\ComponentsApp.App\ComponentsApp.App.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -145,6 +145,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Signal
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpOverrides", "..\Middleware\HttpOverrides\src\Microsoft.AspNetCore.HttpOverrides.csproj", "{FD3A8F8D-2967-4635-86FC-CC49BAF651C1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SignalR.Protocols.Json", "common\Protocols.Json\src\Microsoft.AspNetCore.SignalR.Protocols.Json.csproj", "{BB52C0FB-19FD-485A-9EBD-3FC173ECAEA0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -399,6 +401,10 @@ Global
|
|||
{FD3A8F8D-2967-4635-86FC-CC49BAF651C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FD3A8F8D-2967-4635-86FC-CC49BAF651C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FD3A8F8D-2967-4635-86FC-CC49BAF651C1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BB52C0FB-19FD-485A-9EBD-3FC173ECAEA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BB52C0FB-19FD-485A-9EBD-3FC173ECAEA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BB52C0FB-19FD-485A-9EBD-3FC173ECAEA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BB52C0FB-19FD-485A-9EBD-3FC173ECAEA0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -468,6 +474,7 @@ Global
|
|||
{3BE66897-A7E7-4AC8-B2EF-516366A6710F} = {1C8016A8-F362-45C7-9EA9-A1CCE7918F2F}
|
||||
{762A7DD1-E45E-4EA3-8109-521E844AE613} = {1C8016A8-F362-45C7-9EA9-A1CCE7918F2F}
|
||||
{FD3A8F8D-2967-4635-86FC-CC49BAF651C1} = {EDE8E45E-A5D0-4F0E-B72C-7CC14146C60A}
|
||||
{BB52C0FB-19FD-485A-9EBD-3FC173ECAEA0} = {9FCD621E-E710-4991-B45C-1BABC977BEEC}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {7945A4E4-ACDB-4F6E-95CA-6AC6E7C2CD59}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Client for ASP.NET Core SignalR</Description>
|
||||
|
|
|
|||
|
|
@ -1333,7 +1333,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Returning object from Hub method not support by System.Text.Json yet")]
|
||||
public async Task CheckHttpConnectionFeatures()
|
||||
{
|
||||
using (StartServer<Startup>(out var server))
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Objects not supported yet")]
|
||||
[LogLevel(LogLevel.Trace)]
|
||||
public async Task StreamsObjectsToServer()
|
||||
{
|
||||
|
|
@ -361,7 +361,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
|||
await hubConnection.StartAsync().OrTimeout();
|
||||
|
||||
var channel = Channel.CreateUnbounded<int>();
|
||||
var invokeTask = hubConnection.InvokeAsync<object>("UploadMethod", channel.Reader);
|
||||
var invokeTask = hubConnection.InvokeAsync<long>("UploadMethod", channel.Reader);
|
||||
var invocation = await connection.ReadSentJsonAsync().OrTimeout();
|
||||
Assert.Equal(HubProtocolConstants.InvocationMessageType, invocation["type"]);
|
||||
var id = invocation["invocationId"];
|
||||
|
|
@ -408,10 +408,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
|||
try
|
||||
{
|
||||
await invokeTask;
|
||||
Assert.True(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
Assert.Equal(typeof(Newtonsoft.Json.JsonSerializationException), ex.GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,21 +111,19 @@ namespace Microsoft.AspNetCore.Http.Connections
|
|||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonTokenType.PropertyName:
|
||||
var memberName = reader.ValueSpan;
|
||||
|
||||
if (memberName.SequenceEqual(UrlPropertyNameBytes))
|
||||
if (reader.TextEquals(UrlPropertyNameBytes))
|
||||
{
|
||||
url = reader.ReadAsString(UrlPropertyName);
|
||||
}
|
||||
else if (memberName.SequenceEqual(AccessTokenPropertyNameBytes))
|
||||
else if (reader.TextEquals(AccessTokenPropertyNameBytes))
|
||||
{
|
||||
accessToken = reader.ReadAsString(AccessTokenPropertyName);
|
||||
}
|
||||
else if (memberName.SequenceEqual(ConnectionIdPropertyNameBytes))
|
||||
else if (reader.TextEquals(ConnectionIdPropertyNameBytes))
|
||||
{
|
||||
connectionId = reader.ReadAsString(ConnectionIdPropertyName);
|
||||
}
|
||||
else if (memberName.SequenceEqual(AvailableTransportsPropertyNameBytes))
|
||||
else if (reader.TextEquals(AvailableTransportsPropertyNameBytes))
|
||||
{
|
||||
reader.CheckRead();
|
||||
reader.EnsureArrayStart();
|
||||
|
|
@ -143,11 +141,11 @@ namespace Microsoft.AspNetCore.Http.Connections
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (memberName.SequenceEqual(ErrorPropertyNameBytes))
|
||||
else if (reader.TextEquals(ErrorPropertyNameBytes))
|
||||
{
|
||||
error = reader.ReadAsString(ErrorPropertyName);
|
||||
}
|
||||
else if (memberName.SequenceEqual(ProtocolVersionPropertyNameBytes))
|
||||
else if (reader.TextEquals(ProtocolVersionPropertyNameBytes))
|
||||
{
|
||||
throw new InvalidOperationException("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
<Reference Include="Microsoft.AspNetCore.Routing" />
|
||||
<Reference Include="Microsoft.AspNetCore.WebSockets" />
|
||||
<Reference Include="Microsoft.Extensions.ValueStopwatch.Sources" />
|
||||
<Reference Include="Newtonsoft.Json" />
|
||||
<Reference Include="System.Security.Principal.Windows" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
<Reference Include="Microsoft.AspNetCore.Routing" />
|
||||
<Reference Include="Microsoft.AspNetCore.WebSockets" />
|
||||
<Reference Include="Microsoft.Extensions.ValueStopwatch.Sources" />
|
||||
<Reference Include="Newtonsoft.Json" />
|
||||
<Reference Include="System.Security.Principal.Windows" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<Project>
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);CS3021</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<!-- This file is automatically generated. -->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<Compile Include="Microsoft.AspNetCore.SignalR.Protocols.Json.netstandard2.0.cs" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Common" />
|
||||
<Reference Include="Microsoft.Bcl.Json.Sources" />
|
||||
<Reference Include="System.Buffers" />
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
|
||||
<Compile Include="Microsoft.AspNetCore.SignalR.Protocols.Json.netcoreapp3.0.cs" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Common" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||
{
|
||||
public sealed partial class JsonHubProtocol : Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol
|
||||
{
|
||||
public JsonHubProtocol() { }
|
||||
public int MinorVersion { get { throw null; } }
|
||||
public string Name { get { throw null; } }
|
||||
public Microsoft.AspNetCore.Connections.TransferFormat TransferFormat { get { throw null; } }
|
||||
public int Version { get { throw null; } }
|
||||
public System.ReadOnlyMemory<byte> GetMessageBytes(Microsoft.AspNetCore.SignalR.Protocol.HubMessage message) { throw null; }
|
||||
public bool IsVersionSupported(int version) { throw null; }
|
||||
public bool TryParseMessage(ref System.Buffers.ReadOnlySequence<byte> input, Microsoft.AspNetCore.SignalR.IInvocationBinder binder, out Microsoft.AspNetCore.SignalR.Protocol.HubMessage message) { throw null; }
|
||||
public void WriteMessage(Microsoft.AspNetCore.SignalR.Protocol.HubMessage message, System.Buffers.IBufferWriter<byte> output) { }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static partial class JsonProtocolDependencyInjectionExtensions
|
||||
{
|
||||
public static TBuilder AddJsonProtocol<TBuilder>(this TBuilder builder) where TBuilder : Microsoft.AspNetCore.SignalR.ISignalRBuilder { throw null; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||
{
|
||||
public sealed partial class JsonHubProtocol : Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol
|
||||
{
|
||||
public JsonHubProtocol() { }
|
||||
public int MinorVersion { get { throw null; } }
|
||||
public string Name { get { throw null; } }
|
||||
public Microsoft.AspNetCore.Connections.TransferFormat TransferFormat { get { throw null; } }
|
||||
public int Version { get { throw null; } }
|
||||
public System.ReadOnlyMemory<byte> GetMessageBytes(Microsoft.AspNetCore.SignalR.Protocol.HubMessage message) { throw null; }
|
||||
public bool IsVersionSupported(int version) { throw null; }
|
||||
public bool TryParseMessage(ref System.Buffers.ReadOnlySequence<byte> input, Microsoft.AspNetCore.SignalR.IInvocationBinder binder, out Microsoft.AspNetCore.SignalR.Protocol.HubMessage message) { throw null; }
|
||||
public void WriteMessage(Microsoft.AspNetCore.SignalR.Protocol.HubMessage message, System.Buffers.IBufferWriter<byte> output) { }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static partial class JsonProtocolDependencyInjectionExtensions
|
||||
{
|
||||
public static TBuilder AddJsonProtocol<TBuilder>(this TBuilder builder) where TBuilder : Microsoft.AspNetCore.SignalR.ISignalRBuilder { throw null; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// 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 Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="ISignalRBuilder"/>.
|
||||
/// </summary>
|
||||
public static class JsonProtocolDependencyInjectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables the JSON protocol for SignalR.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This has no effect if the JSON protocol has already been enabled.
|
||||
/// </remarks>
|
||||
/// <param name="builder">The <see cref="ISignalRBuilder"/> representing the SignalR server to add JSON protocol support to.</param>
|
||||
/// <returns>The value of <paramref name="builder"/></returns>
|
||||
public static TBuilder AddJsonProtocol<TBuilder>(this TBuilder builder) where TBuilder : ISignalRBuilder
|
||||
{
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IHubProtocol, JsonHubProtocol>());
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Implements the SignalR Hub Protocol using System.Text.Json.</Description>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
|
||||
<IsAspNetCoreApp>true</IsAspNetCoreApp>
|
||||
<RootNamespace>Microsoft.AspNetCore.SignalR</RootNamespace>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<IsShippingPackage>true</IsShippingPackage>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(SignalRSharedSourceRoot)SystemTextJsonExtensions.cs" Link="Internal\SystemTextJsonExtensions.cs" />
|
||||
<Compile Include="$(SignalRSharedSourceRoot)TextMessageFormatter.cs" Link="TextMessageFormatter.cs" />
|
||||
<Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="TextMessageParser.cs" />
|
||||
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextReader.cs" Link="Utf8BufferTextReader.cs" />
|
||||
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Utf8BufferTextWriter.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Common" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'" >
|
||||
<Reference Include="Microsoft.Bcl.Json.Sources" />
|
||||
<Reference Include="System.Buffers" />
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,760 @@
|
|||
// 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.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the SignalR Hub Protocol using System.Text.Json.
|
||||
/// </summary>
|
||||
public sealed class JsonHubProtocol : IHubProtocol
|
||||
{
|
||||
// Use C#7.3's ReadOnlySpan<byte> optimization for static data https://vcsjones.com/2019/02/01/csharp-readonly-span-bytes-static/
|
||||
private const string ResultPropertyName = "result";
|
||||
private static ReadOnlySpan<byte> ResultPropertyNameBytes => new byte[] { (byte)'r', (byte)'e', (byte)'s', (byte)'u', (byte)'l', (byte)'t' };
|
||||
private const string ItemPropertyName = "item";
|
||||
private static ReadOnlySpan<byte> ItemPropertyNameBytes => new byte[] { (byte)'i', (byte)'t', (byte)'e', (byte)'m' };
|
||||
private const string InvocationIdPropertyName = "invocationId";
|
||||
private static ReadOnlySpan<byte> InvocationIdPropertyNameBytes => new byte[] { (byte)'i', (byte)'n', (byte)'v', (byte)'o', (byte)'c', (byte)'a', (byte)'t', (byte)'i', (byte)'o', (byte)'n', (byte)'I', (byte)'d' };
|
||||
private const string StreamIdsPropertyName = "streamIds";
|
||||
private static ReadOnlySpan<byte> StreamIdsPropertyNameBytes => new byte[] { (byte)'s', (byte)'t', (byte)'r', (byte)'e', (byte)'a', (byte)'m', (byte)'I', (byte)'d', (byte)'s' };
|
||||
private const string TypePropertyName = "type";
|
||||
private static ReadOnlySpan<byte> TypePropertyNameBytes => new byte[] { (byte)'t', (byte)'y', (byte)'p', (byte)'e' };
|
||||
private const string ErrorPropertyName = "error";
|
||||
private static ReadOnlySpan<byte> ErrorPropertyNameBytes => new byte[] { (byte)'e', (byte)'r', (byte)'r', (byte)'o', (byte)'r' };
|
||||
private const string TargetPropertyName = "target";
|
||||
private static ReadOnlySpan<byte> TargetPropertyNameBytes => new byte[] { (byte)'t', (byte)'a', (byte)'r', (byte)'g', (byte)'e', (byte)'t' };
|
||||
private const string ArgumentsPropertyName = "arguments";
|
||||
private static ReadOnlySpan<byte> ArgumentsPropertyNameBytes => new byte[] { (byte)'a', (byte)'r', (byte)'g', (byte)'u', (byte)'m', (byte)'e', (byte)'n', (byte)'t', (byte)'s' };
|
||||
private const string HeadersPropertyName = "headers";
|
||||
private static ReadOnlySpan<byte> HeadersPropertyNameBytes => new byte[] { (byte)'h', (byte)'e', (byte)'a', (byte)'d', (byte)'e', (byte)'r', (byte)'s' };
|
||||
|
||||
private static readonly string ProtocolName = "json";
|
||||
private static readonly int ProtocolVersion = 1;
|
||||
private static readonly int ProtocolMinorVersion = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonHubProtocol"/> class.
|
||||
/// </summary>
|
||||
public JsonHubProtocol()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => ProtocolName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Version => ProtocolVersion;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int MinorVersion => ProtocolMinorVersion;
|
||||
|
||||
/// <inheritdoc />
|
||||
public TransferFormat TransferFormat => TransferFormat.Text;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsVersionSupported(int version)
|
||||
{
|
||||
return version == Version;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryParseMessage(ref ReadOnlySequence<byte> input, IInvocationBinder binder, out HubMessage message)
|
||||
{
|
||||
if (!TextMessageParser.TryParseMessage(ref input, out var payload))
|
||||
{
|
||||
message = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
message = ParseMessage(payload, binder);
|
||||
|
||||
return message != null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteMessage(HubMessage message, IBufferWriter<byte> output)
|
||||
{
|
||||
WriteMessageCore(message, output);
|
||||
TextMessageFormatter.WriteRecordSeparator(output);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReadOnlyMemory<byte> GetMessageBytes(HubMessage message)
|
||||
{
|
||||
return HubProtocolExtensions.GetMessageBytes(this, message);
|
||||
}
|
||||
|
||||
private HubMessage ParseMessage(ReadOnlySequence<byte> input, IInvocationBinder binder)
|
||||
{
|
||||
try
|
||||
{
|
||||
// We parse using the Utf8JsonReader directly but this has a problem. Some of our properties are dependent on other properties
|
||||
// and since reading the json might be unordered, we need to store the parsed content as JsonDocument to re-parse when true types are known.
|
||||
// if we're lucky and the state we need to directly parse is available, then we'll use it.
|
||||
|
||||
int? type = null;
|
||||
string invocationId = null;
|
||||
string target = null;
|
||||
string error = null;
|
||||
var hasItem = false;
|
||||
object item = null;
|
||||
var hasResult = false;
|
||||
object result = null;
|
||||
var hasArguments = false;
|
||||
object[] arguments = null;
|
||||
string[] streamIds = null;
|
||||
JsonDocument argumentsToken = null;
|
||||
JsonDocument itemsToken = null;
|
||||
JsonDocument resultToken = null;
|
||||
ExceptionDispatchInfo argumentBindingException = null;
|
||||
Dictionary<string, string> headers = null;
|
||||
var completed = false;
|
||||
|
||||
var reader = new Utf8JsonReader(input, isFinalBlock: true, state: default);
|
||||
|
||||
reader.CheckRead();
|
||||
|
||||
// We're always parsing a JSON object
|
||||
reader.EnsureObjectStart();
|
||||
|
||||
do
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonTokenType.PropertyName:
|
||||
if (reader.TextEquals(TypePropertyNameBytes))
|
||||
{
|
||||
type = reader.ReadAsInt32(TypePropertyName);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
throw new InvalidDataException($"Expected '{TypePropertyName}' to be of type {JsonTokenType.Number}.");
|
||||
}
|
||||
}
|
||||
else if (reader.TextEquals(InvocationIdPropertyNameBytes))
|
||||
{
|
||||
invocationId = reader.ReadAsString(InvocationIdPropertyName);
|
||||
}
|
||||
else if (reader.TextEquals(StreamIdsPropertyNameBytes))
|
||||
{
|
||||
reader.CheckRead();
|
||||
|
||||
if (reader.TokenType != JsonTokenType.StartArray)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Expected '{StreamIdsPropertyName}' to be of type {SystemTextJsonExtensions.GetTokenString(JsonTokenType.StartArray)}.");
|
||||
}
|
||||
|
||||
var newStreamIds = new List<string>();
|
||||
reader.Read();
|
||||
while (reader.TokenType != JsonTokenType.EndArray)
|
||||
{
|
||||
newStreamIds.Add(reader.GetString());
|
||||
reader.Read();
|
||||
}
|
||||
|
||||
streamIds = newStreamIds.ToArray();
|
||||
}
|
||||
else if (reader.TextEquals(TargetPropertyNameBytes))
|
||||
{
|
||||
target = reader.ReadAsString(TargetPropertyName);
|
||||
}
|
||||
else if (reader.TextEquals(ErrorPropertyNameBytes))
|
||||
{
|
||||
error = reader.ReadAsString(ErrorPropertyName);
|
||||
}
|
||||
else if (reader.TextEquals(ResultPropertyNameBytes))
|
||||
{
|
||||
hasResult = true;
|
||||
|
||||
reader.CheckRead();
|
||||
|
||||
if (string.IsNullOrEmpty(invocationId))
|
||||
{
|
||||
// If we don't have an invocation id then we need to store it as a JsonDocument so we can parse it later
|
||||
resultToken = JsonDocument.ParseValue(ref reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we have an invocation id already we can parse the end result
|
||||
var returnType = binder.GetReturnType(invocationId);
|
||||
if (reader.TokenType != JsonTokenType.Null)
|
||||
{
|
||||
using var token = JsonDocument.ParseValue(ref reader);
|
||||
result = BindType(token.RootElement, returnType);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (reader.TextEquals(ItemPropertyNameBytes))
|
||||
{
|
||||
reader.CheckRead();
|
||||
|
||||
hasItem = true;
|
||||
|
||||
string id = null;
|
||||
if (!string.IsNullOrEmpty(invocationId))
|
||||
{
|
||||
id = invocationId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we don't have an id yet then we need to store it as a JsonDocument to parse later
|
||||
itemsToken = JsonDocument.ParseValue(ref reader);
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var itemType = binder.GetStreamItemType(id);
|
||||
if (reader.TokenType != JsonTokenType.Null)
|
||||
{
|
||||
using var token = JsonDocument.ParseValue(ref reader);
|
||||
item = BindType(token.RootElement, itemType);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new StreamBindingFailureMessage(id, ExceptionDispatchInfo.Capture(ex));
|
||||
}
|
||||
}
|
||||
else if (reader.TextEquals(ArgumentsPropertyNameBytes))
|
||||
{
|
||||
reader.CheckRead();
|
||||
|
||||
int initialDepth = reader.CurrentDepth;
|
||||
if (reader.TokenType != JsonTokenType.StartArray)
|
||||
{
|
||||
throw new InvalidDataException($"Expected '{ArgumentsPropertyName}' to be of type {SystemTextJsonExtensions.GetTokenString(JsonTokenType.StartArray)}.");
|
||||
}
|
||||
|
||||
hasArguments = true;
|
||||
|
||||
if (string.IsNullOrEmpty(target))
|
||||
{
|
||||
// We don't know the method name yet so just store the array in JsonDocument
|
||||
argumentsToken = JsonDocument.ParseValue(ref reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var paramTypes = binder.GetParameterTypes(target);
|
||||
using var token = JsonDocument.ParseValue(ref reader);
|
||||
arguments = BindTypes(token.RootElement, paramTypes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
argumentBindingException = ExceptionDispatchInfo.Capture(ex);
|
||||
|
||||
// Could be at any point in argument array JSON when an error is thrown
|
||||
// Read until the end of the argument JSON array
|
||||
while (reader.CurrentDepth == initialDepth && reader.TokenType == JsonTokenType.StartArray ||
|
||||
reader.CurrentDepth > initialDepth)
|
||||
{
|
||||
reader.CheckRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (reader.TextEquals(HeadersPropertyNameBytes))
|
||||
{
|
||||
reader.CheckRead();
|
||||
headers = ReadHeaders(ref reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.CheckRead();
|
||||
reader.Skip();
|
||||
}
|
||||
break;
|
||||
case JsonTokenType.EndObject:
|
||||
completed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (!completed && reader.CheckRead());
|
||||
|
||||
HubMessage message;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case HubProtocolConstants.InvocationMessageType:
|
||||
{
|
||||
if (argumentsToken != null)
|
||||
{
|
||||
// We weren't able to bind the arguments because they came before the 'target', so try to bind now that we've read everything.
|
||||
try
|
||||
{
|
||||
var paramTypes = binder.GetParameterTypes(target);
|
||||
arguments = BindTypes(argumentsToken.RootElement, paramTypes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
argumentBindingException = ExceptionDispatchInfo.Capture(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
argumentsToken.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
message = argumentBindingException != null
|
||||
? new InvocationBindingFailureMessage(invocationId, target, argumentBindingException)
|
||||
: BindInvocationMessage(invocationId, target, arguments, hasArguments, streamIds, binder);
|
||||
}
|
||||
break;
|
||||
case HubProtocolConstants.StreamInvocationMessageType:
|
||||
{
|
||||
if (argumentsToken != null)
|
||||
{
|
||||
// We weren't able to bind the arguments because they came before the 'target', so try to bind now that we've read everything.
|
||||
try
|
||||
{
|
||||
var paramTypes = binder.GetParameterTypes(target);
|
||||
arguments = BindTypes(argumentsToken.RootElement, paramTypes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
argumentBindingException = ExceptionDispatchInfo.Capture(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
argumentsToken.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
message = argumentBindingException != null
|
||||
? new InvocationBindingFailureMessage(invocationId, target, argumentBindingException)
|
||||
: BindStreamInvocationMessage(invocationId, target, arguments, hasArguments, streamIds, binder);
|
||||
}
|
||||
break;
|
||||
case HubProtocolConstants.StreamItemMessageType:
|
||||
if (itemsToken != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var returnType = binder.GetStreamItemType(invocationId);
|
||||
item = BindType(itemsToken.RootElement, returnType);
|
||||
}
|
||||
catch (JsonReaderException ex)
|
||||
{
|
||||
message = new StreamBindingFailureMessage(invocationId, ExceptionDispatchInfo.Capture(ex));
|
||||
break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
itemsToken.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
message = BindStreamItemMessage(invocationId, item, hasItem, binder);
|
||||
break;
|
||||
case HubProtocolConstants.CompletionMessageType:
|
||||
if (resultToken != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var returnType = binder.GetReturnType(invocationId);
|
||||
result = BindType(resultToken.RootElement, returnType);
|
||||
}
|
||||
finally
|
||||
{
|
||||
resultToken.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
message = BindCompletionMessage(invocationId, error, result, hasResult, binder);
|
||||
break;
|
||||
case HubProtocolConstants.CancelInvocationMessageType:
|
||||
message = BindCancelInvocationMessage(invocationId);
|
||||
break;
|
||||
case HubProtocolConstants.PingMessageType:
|
||||
return PingMessage.Instance;
|
||||
case HubProtocolConstants.CloseMessageType:
|
||||
return BindCloseMessage(error);
|
||||
case null:
|
||||
throw new InvalidDataException($"Missing required property '{TypePropertyName}'.");
|
||||
default:
|
||||
// Future protocol changes can add message types, old clients can ignore them
|
||||
return null;
|
||||
}
|
||||
|
||||
return ApplyHeaders(message, headers);
|
||||
}
|
||||
catch (JsonReaderException jrex)
|
||||
{
|
||||
throw new InvalidDataException("Error reading JSON.", jrex);
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ReadHeaders(ref Utf8JsonReader reader)
|
||||
{
|
||||
var headers = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
|
||||
if (reader.TokenType != JsonTokenType.StartObject)
|
||||
{
|
||||
throw new InvalidDataException($"Expected '{HeadersPropertyName}' to be of type {JsonTokenType.StartObject}.");
|
||||
}
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonTokenType.PropertyName:
|
||||
var propertyName = reader.GetString();
|
||||
|
||||
reader.CheckRead();
|
||||
|
||||
if (reader.TokenType != JsonTokenType.String)
|
||||
{
|
||||
throw new InvalidDataException($"Expected header '{propertyName}' to be of type {JsonTokenType.String}.");
|
||||
}
|
||||
|
||||
headers[propertyName] = reader.GetString();
|
||||
break;
|
||||
case JsonTokenType.Comment:
|
||||
break;
|
||||
case JsonTokenType.EndObject:
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidDataException("Unexpected end when reading message headers");
|
||||
}
|
||||
|
||||
private void WriteMessageCore(HubMessage message, IBufferWriter<byte> stream)
|
||||
{
|
||||
var writer = new Utf8JsonWriter(stream);
|
||||
|
||||
writer.WriteStartObject();
|
||||
switch (message)
|
||||
{
|
||||
case InvocationMessage m:
|
||||
WriteMessageType(ref writer, HubProtocolConstants.InvocationMessageType);
|
||||
WriteHeaders(ref writer, m);
|
||||
WriteInvocationMessage(m, ref writer);
|
||||
break;
|
||||
case StreamInvocationMessage m:
|
||||
WriteMessageType(ref writer, HubProtocolConstants.StreamInvocationMessageType);
|
||||
WriteHeaders(ref writer, m);
|
||||
WriteStreamInvocationMessage(m, ref writer);
|
||||
break;
|
||||
case StreamItemMessage m:
|
||||
WriteMessageType(ref writer, HubProtocolConstants.StreamItemMessageType);
|
||||
WriteHeaders(ref writer, m);
|
||||
WriteStreamItemMessage(m, ref writer);
|
||||
break;
|
||||
case CompletionMessage m:
|
||||
WriteMessageType(ref writer, HubProtocolConstants.CompletionMessageType);
|
||||
WriteHeaders(ref writer, m);
|
||||
WriteCompletionMessage(m, ref writer);
|
||||
break;
|
||||
case CancelInvocationMessage m:
|
||||
WriteMessageType(ref writer, HubProtocolConstants.CancelInvocationMessageType);
|
||||
WriteHeaders(ref writer, m);
|
||||
WriteCancelInvocationMessage(m, ref writer);
|
||||
break;
|
||||
case PingMessage _:
|
||||
WriteMessageType(ref writer, HubProtocolConstants.PingMessageType);
|
||||
break;
|
||||
case CloseMessage m:
|
||||
WriteMessageType(ref writer, HubProtocolConstants.CloseMessageType);
|
||||
WriteCloseMessage(m, ref writer);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported message type: {message.GetType().FullName}");
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
private void WriteHeaders(ref Utf8JsonWriter writer, HubInvocationMessage message)
|
||||
{
|
||||
if (message.Headers != null && message.Headers.Count > 0)
|
||||
{
|
||||
writer.WriteStartObject(HeadersPropertyNameBytes, escape: false);
|
||||
foreach (var value in message.Headers)
|
||||
{
|
||||
writer.WriteString(value.Key, value.Value);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCompletionMessage(CompletionMessage message, ref Utf8JsonWriter writer)
|
||||
{
|
||||
WriteInvocationId(message, ref writer);
|
||||
if (!string.IsNullOrEmpty(message.Error))
|
||||
{
|
||||
writer.WriteString(ErrorPropertyNameBytes, message.Error, escape: false);
|
||||
}
|
||||
else if (message.HasResult)
|
||||
{
|
||||
using var token = GetParsedObject(message.Result, message.Result?.GetType());
|
||||
token.RootElement.WriteAsProperty(ResultPropertyNameBytes, ref writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCancelInvocationMessage(CancelInvocationMessage message, ref Utf8JsonWriter writer)
|
||||
{
|
||||
WriteInvocationId(message, ref writer);
|
||||
}
|
||||
|
||||
private void WriteStreamItemMessage(StreamItemMessage message, ref Utf8JsonWriter writer)
|
||||
{
|
||||
WriteInvocationId(message, ref writer);
|
||||
|
||||
using var token = GetParsedObject(message.Item, message.Item?.GetType());
|
||||
token.RootElement.WriteAsProperty(ItemPropertyNameBytes, ref writer);
|
||||
}
|
||||
|
||||
private void WriteInvocationMessage(InvocationMessage message, ref Utf8JsonWriter writer)
|
||||
{
|
||||
WriteInvocationId(message, ref writer);
|
||||
writer.WriteString(TargetPropertyNameBytes, message.Target, escape: false);
|
||||
|
||||
WriteArguments(message.Arguments, ref writer);
|
||||
|
||||
WriteStreamIds(message.StreamIds, ref writer);
|
||||
}
|
||||
|
||||
private void WriteStreamInvocationMessage(StreamInvocationMessage message, ref Utf8JsonWriter writer)
|
||||
{
|
||||
WriteInvocationId(message, ref writer);
|
||||
writer.WriteString(TargetPropertyNameBytes, message.Target, escape: false);
|
||||
|
||||
WriteArguments(message.Arguments, ref writer);
|
||||
|
||||
WriteStreamIds(message.StreamIds, ref writer);
|
||||
}
|
||||
|
||||
private void WriteCloseMessage(CloseMessage message, ref Utf8JsonWriter writer)
|
||||
{
|
||||
if (message.Error != null)
|
||||
{
|
||||
writer.WriteString(ErrorPropertyNameBytes, message.Error, escape: false);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteArguments(object[] arguments, ref Utf8JsonWriter writer)
|
||||
{
|
||||
writer.WriteStartArray(ArgumentsPropertyNameBytes, escape: false);
|
||||
foreach (var argument in arguments)
|
||||
{
|
||||
var type = argument?.GetType();
|
||||
if (type == typeof(DateTime))
|
||||
{
|
||||
writer.WriteStringValue((DateTime)argument);
|
||||
}
|
||||
else if (type == typeof(DateTimeOffset))
|
||||
{
|
||||
writer.WriteStringValue((DateTimeOffset)argument);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var token = GetParsedObject(argument, type);
|
||||
token.RootElement.WriteAsValue(ref writer);
|
||||
}
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
private JsonDocument GetParsedObject(object obj, Type type)
|
||||
{
|
||||
var bytes = JsonSerializer.ToBytes(obj, type);
|
||||
var token = JsonDocument.Parse(bytes);
|
||||
return token;
|
||||
}
|
||||
|
||||
private void WriteStreamIds(string[] streamIds, ref Utf8JsonWriter writer)
|
||||
{
|
||||
if (streamIds == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
writer.WriteStartArray(StreamIdsPropertyNameBytes, escape: false);
|
||||
foreach (var streamId in streamIds)
|
||||
{
|
||||
writer.WriteStringValue(streamId);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
private static void WriteInvocationId(HubInvocationMessage message, ref Utf8JsonWriter writer)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(message.InvocationId))
|
||||
{
|
||||
writer.WriteString(InvocationIdPropertyNameBytes, message.InvocationId, escape: false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteMessageType(ref Utf8JsonWriter writer, int type)
|
||||
{
|
||||
writer.WriteNumber(TypePropertyNameBytes, type, escape: false);
|
||||
}
|
||||
|
||||
private HubMessage BindCancelInvocationMessage(string invocationId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(invocationId))
|
||||
{
|
||||
throw new InvalidDataException($"Missing required property '{InvocationIdPropertyName}'.");
|
||||
}
|
||||
|
||||
return new CancelInvocationMessage(invocationId);
|
||||
}
|
||||
|
||||
private HubMessage BindCompletionMessage(string invocationId, string error, object result, bool hasResult, IInvocationBinder binder)
|
||||
{
|
||||
if (string.IsNullOrEmpty(invocationId))
|
||||
{
|
||||
throw new InvalidDataException($"Missing required property '{InvocationIdPropertyName}'.");
|
||||
}
|
||||
|
||||
if (error != null && hasResult)
|
||||
{
|
||||
throw new InvalidDataException("The 'error' and 'result' properties are mutually exclusive.");
|
||||
}
|
||||
|
||||
if (hasResult)
|
||||
{
|
||||
return new CompletionMessage(invocationId, error, result, hasResult: true);
|
||||
}
|
||||
|
||||
return new CompletionMessage(invocationId, error, result: null, hasResult: false);
|
||||
}
|
||||
|
||||
private HubMessage BindStreamItemMessage(string invocationId, object item, bool hasItem, IInvocationBinder binder)
|
||||
{
|
||||
if (string.IsNullOrEmpty(invocationId))
|
||||
{
|
||||
throw new InvalidDataException($"Missing required property '{InvocationIdPropertyName}'.");
|
||||
}
|
||||
|
||||
if (!hasItem)
|
||||
{
|
||||
throw new InvalidDataException($"Missing required property '{ItemPropertyName}'.");
|
||||
}
|
||||
|
||||
return new StreamItemMessage(invocationId, item);
|
||||
}
|
||||
|
||||
private HubMessage BindStreamInvocationMessage(string invocationId, string target, object[] arguments, bool hasArguments, string[] streamIds, IInvocationBinder binder)
|
||||
{
|
||||
if (string.IsNullOrEmpty(invocationId))
|
||||
{
|
||||
throw new InvalidDataException($"Missing required property '{InvocationIdPropertyName}'.");
|
||||
}
|
||||
|
||||
if (!hasArguments)
|
||||
{
|
||||
throw new InvalidDataException($"Missing required property '{ArgumentsPropertyName}'.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(target))
|
||||
{
|
||||
throw new InvalidDataException($"Missing required property '{TargetPropertyName}'.");
|
||||
}
|
||||
|
||||
return new StreamInvocationMessage(invocationId, target, arguments, streamIds);
|
||||
}
|
||||
|
||||
private HubMessage BindInvocationMessage(string invocationId, string target, object[] arguments, bool hasArguments, string[] streamIds, IInvocationBinder binder)
|
||||
{
|
||||
if (string.IsNullOrEmpty(target))
|
||||
{
|
||||
throw new InvalidDataException($"Missing required property '{TargetPropertyName}'.");
|
||||
}
|
||||
|
||||
if (!hasArguments)
|
||||
{
|
||||
throw new InvalidDataException($"Missing required property '{ArgumentsPropertyName}'.");
|
||||
}
|
||||
|
||||
return new InvocationMessage(invocationId, target, arguments, streamIds);
|
||||
}
|
||||
|
||||
private object BindType(JsonElement jsonObject, Type type)
|
||||
{
|
||||
if (type == typeof(DateTime))
|
||||
{
|
||||
return jsonObject.GetDateTime();
|
||||
}
|
||||
else if (type == typeof(DateTimeOffset))
|
||||
{
|
||||
return jsonObject.GetDateTimeOffset();
|
||||
}
|
||||
|
||||
if (jsonObject.Type == JsonValueType.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return JsonSerializer.Parse(jsonObject.GetRawText(), type);
|
||||
}
|
||||
|
||||
private object[] BindTypes(JsonElement jsonArray, IReadOnlyList<Type> paramTypes)
|
||||
{
|
||||
object[] arguments = null;
|
||||
var paramIndex = 0;
|
||||
var argumentsCount = jsonArray.GetArrayLength();
|
||||
var paramCount = paramTypes.Count;
|
||||
|
||||
if (argumentsCount != paramCount)
|
||||
{
|
||||
throw new InvalidDataException($"Invocation provides {argumentsCount} argument(s) but target expects {paramCount}.");
|
||||
}
|
||||
|
||||
foreach (var element in jsonArray.EnumerateArray())
|
||||
{
|
||||
if (arguments == null)
|
||||
{
|
||||
arguments = new object[paramCount];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
arguments[paramIndex] = BindType(element, paramTypes[paramIndex]);
|
||||
paramIndex++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidDataException("Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return arguments ?? Array.Empty<object>();
|
||||
}
|
||||
|
||||
private CloseMessage BindCloseMessage(string error)
|
||||
{
|
||||
// An empty string is still an error
|
||||
if (error == null)
|
||||
{
|
||||
return CloseMessage.Empty;
|
||||
}
|
||||
|
||||
var message = new CloseMessage(error);
|
||||
return message;
|
||||
}
|
||||
|
||||
private HubMessage ApplyHeaders(HubMessage message, Dictionary<string, string> headers)
|
||||
{
|
||||
if (headers != null && message is HubInvocationMessage invocationMessage)
|
||||
{
|
||||
invocationMessage.Headers = headers;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
|
|||
|
||||
if (reader.TokenType != JsonToken.StartArray)
|
||||
{
|
||||
throw new InvalidDataException($"Expected '{ArgumentsPropertyName}' to be of type {JTokenType.Array}.");
|
||||
throw new InvalidDataException($"Expected '{StreamIdsPropertyName}' to be of type {JTokenType.Array}.");
|
||||
}
|
||||
|
||||
var newStreamIds = new List<string>();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
// 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.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Microsoft.AspNetCore.Internal
|
||||
|
|
@ -24,10 +22,15 @@ namespace Microsoft.AspNetCore.Internal
|
|||
{
|
||||
if (reader.TokenType != JsonTokenType.StartObject)
|
||||
{
|
||||
throw new InvalidDataException($"Unexpected JSON Token Type '{GetTokenString(reader.TokenType)}'. Expected a JSON Object.");
|
||||
throw new InvalidDataException($"Unexpected JSON Token Type '{reader.GetTokenString()}'. Expected a JSON Object.");
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetTokenString(this ref Utf8JsonReader reader)
|
||||
{
|
||||
return GetTokenString(reader.TokenType);
|
||||
}
|
||||
|
||||
public static string GetTokenString(JsonTokenType tokenType)
|
||||
{
|
||||
switch (tokenType)
|
||||
|
|
@ -50,7 +53,7 @@ namespace Microsoft.AspNetCore.Internal
|
|||
{
|
||||
if (reader.TokenType != JsonTokenType.StartArray)
|
||||
{
|
||||
throw new InvalidDataException($"Unexpected JSON Token Type '{GetTokenString(reader.TokenType)}'. Expected a JSON Array.");
|
||||
throw new InvalidDataException($"Unexpected JSON Token Type '{reader.GetTokenString()}'. Expected a JSON Array.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -119,19 +119,17 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
|
|||
{
|
||||
if (reader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
var memberName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
|
||||
|
||||
if (memberName.SequenceEqual(TypePropertyNameBytes))
|
||||
if (reader.TextEquals(TypePropertyNameBytes))
|
||||
{
|
||||
// a handshake response does not have a type
|
||||
// check the incoming message was not any other type of message
|
||||
throw new InvalidDataException("Expected a handshake response from the server.");
|
||||
}
|
||||
else if (memberName.SequenceEqual(ErrorPropertyNameBytes))
|
||||
else if (reader.TextEquals(ErrorPropertyNameBytes))
|
||||
{
|
||||
error = reader.ReadAsString(ErrorPropertyName);
|
||||
}
|
||||
else if (memberName.SequenceEqual(MinorVersionPropertyNameBytes))
|
||||
else if (reader.TextEquals(MinorVersionPropertyNameBytes))
|
||||
{
|
||||
minorVersion = reader.ReadAsInt32(MinorVersionPropertyName);
|
||||
}
|
||||
|
|
@ -180,13 +178,11 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
|
|||
{
|
||||
if (reader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
var memberName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
|
||||
|
||||
if (memberName.SequenceEqual(ProtocolPropertyNameBytes))
|
||||
if (reader.TextEquals(ProtocolPropertyNameBytes))
|
||||
{
|
||||
protocol = reader.ReadAsString(ProtocolPropertyName);
|
||||
}
|
||||
else if (memberName.SequenceEqual(ProtocolVersionPropertyNameBytes))
|
||||
else if (reader.TextEquals(ProtocolVersionPropertyNameBytes))
|
||||
{
|
||||
protocolVersion = reader.ReadAsInt32(ProtocolVersionPropertyName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,131 +9,40 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
|
||||
{
|
||||
using static HubMessageHelpers;
|
||||
|
||||
public class JsonHubProtocolTests
|
||||
public class JsonHubProtocolTests : JsonHubProtocolTestsBase
|
||||
{
|
||||
private static readonly IDictionary<string, string> TestHeaders = new Dictionary<string, string>
|
||||
{
|
||||
{ "Foo", "Bar" },
|
||||
{ "KeyWith\nNew\r\nLines", "Still Works" },
|
||||
{ "ValueWithNewLines", "Also\nWorks\r\nFine" },
|
||||
};
|
||||
|
||||
// It's cleaner to do this as a prefix and use concatenation rather than string interpolation because JSON is already filled with '{'s.
|
||||
private static readonly string SerializedHeaders = "\"headers\":{\"Foo\":\"Bar\",\"KeyWith\\nNew\\r\\nLines\":\"Still Works\",\"ValueWithNewLines\":\"Also\\nWorks\\r\\nFine\"}";
|
||||
|
||||
public static IDictionary<string, JsonProtocolTestData> ProtocolTestData => new[]
|
||||
{
|
||||
new JsonProtocolTestData("InvocationMessage_HasInvocationId", new InvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":1,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasBoolArgument", new InvocationMessage(null, "Target", new object[] { true }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[true]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasNullArgument", new InvocationMessage(null, "Target", new object[] { null }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[null]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, NullValueHandling.Include, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, NullValueHandling.Include, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasStreamArgument", new InvocationMessage(null, "Target", Array.Empty<object>(), new string[] { "__test_id__" }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[],\"streamIds\":[\"__test_id__\"]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasStreamAndNormalArgument", new InvocationMessage(null, "Target", new object[] { 42 }, new string[] { "__test_id__" }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[42],\"streamIds\":[\"__test_id__\"]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasMultipleStreams", new InvocationMessage(null, "Target", Array.Empty<object>(), new string[] { "__test_id__", "__test_id2__" }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[],\"streamIds\":[\"__test_id__\",\"__test_id2__\"]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasHeaders", AddHeaders(TestHeaders, new InvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f })), true, NullValueHandling.Ignore, "{\"type\":1," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgument", new InvocationMessage("Method", new object[] { DateTimeOffset.Parse("2016-05-10T13:51:20+12:34") }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"),
|
||||
|
||||
new JsonProtocolTestData("StreamItemMessage_HasIntegerItem", new StreamItemMessage("123", 1), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":1}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasStringItem", new StreamItemMessage("123", "Foo"), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":\"Foo\"}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasFloatItem", new StreamItemMessage("123", 2.0f), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":2.0}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasBoolItem", new StreamItemMessage("123", true), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":true}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasNullItem", new StreamItemMessage("123", null), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":null}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new CustomObject()), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, NullValueHandling.Include, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new CustomObject()), true, NullValueHandling.Include, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new CustomObject())), true, NullValueHandling.Include, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"),
|
||||
|
||||
new JsonProtocolTestData("CompletionMessage_HasIntegerResult", CompletionMessage.WithResult("123", 1), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":1}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasStringResult", CompletionMessage.WithResult("123", "Foo"), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":\"Foo\"}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasFloatResult", CompletionMessage.WithResult("123", 2.0f), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":2.0}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasBoolResult", CompletionMessage.WithResult("123", true), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":true}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasNullResult", CompletionMessage.WithResult("123", null), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":null}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new CustomObject()), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, NullValueHandling.Include, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new CustomObject()), true, NullValueHandling.Include, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasTestHeadersAndCustomItemResult", AddHeaders(TestHeaders, CompletionMessage.WithResult("123", new CustomObject())), true, NullValueHandling.Include, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasError", CompletionMessage.WithError("123", "Whoops!"), false, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"error\":\"Whoops!\"}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasErrorAndHeaders", AddHeaders(TestHeaders, CompletionMessage.WithError("123", "Whoops!")), false, NullValueHandling.Ignore, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"error\":\"Whoops!\"}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\"}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasErrorAndHeadersAndCamelCase", AddHeaders(TestHeaders, CompletionMessage.Empty("123")), true, NullValueHandling.Ignore, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\"}"),
|
||||
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasInvocationId", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasBoolArgument", new StreamInvocationMessage("123", "Target", new object[] { true }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[true]}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasNullArgument", new StreamInvocationMessage("123", "Target", new object[] { null }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[null]}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasStreamArgument", new StreamInvocationMessage("123", "Target", Array.Empty<object>(), new string[] { "__test_id__" }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[],\"streamIds\":[\"__test_id__\"]}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, NullValueHandling.Include, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, NullValueHandling.Include, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() })), true, NullValueHandling.Include, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"),
|
||||
|
||||
new JsonProtocolTestData("CancelInvocationMessage_HasInvocationId", new CancelInvocationMessage("123"), true, NullValueHandling.Ignore, "{\"type\":5,\"invocationId\":\"123\"}"),
|
||||
new JsonProtocolTestData("CancelInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new CancelInvocationMessage("123")), true, NullValueHandling.Ignore, "{\"type\":5," + SerializedHeaders + ",\"invocationId\":\"123\"}"),
|
||||
|
||||
new JsonProtocolTestData("PingMessage", PingMessage.Instance, true, NullValueHandling.Ignore, "{\"type\":6}"),
|
||||
|
||||
new JsonProtocolTestData("CloseMessage", CloseMessage.Empty, false, NullValueHandling.Ignore, "{\"type\":7}"),
|
||||
new JsonProtocolTestData("CloseMessage_HasError", new CloseMessage("Error!"), false, NullValueHandling.Ignore, "{\"type\":7,\"error\":\"Error!\"}"),
|
||||
new JsonProtocolTestData("CloseMessage_HasErrorWithCamelCase", new CloseMessage("Error!"), true, NullValueHandling.Ignore, "{\"type\":7,\"error\":\"Error!\"}"),
|
||||
new JsonProtocolTestData("CloseMessage_HasErrorEmptyString", new CloseMessage(""), false, NullValueHandling.Ignore, "{\"type\":7,\"error\":\"\"}"),
|
||||
|
||||
}.ToDictionary(t => t.Name);
|
||||
|
||||
public static IEnumerable<object[]> ProtocolTestDataNames => ProtocolTestData.Keys.Select(name => new object[] { name });
|
||||
|
||||
public static IDictionary<string, JsonProtocolTestData> OutOfOrderJsonTestData => new[]
|
||||
{
|
||||
new JsonProtocolTestData("InvocationMessage_StringIsoDateArgumentFirst", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), false, NullValueHandling.Ignore, "{ \"arguments\": [\"2016-05-10T13:51:20+12:34\"], \"type\":1, \"target\": \"Method\" }"),
|
||||
new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgumentFirst", new InvocationMessage("Method", new object[] { DateTimeOffset.Parse("2016-05-10T13:51:20+12:34") }), false, NullValueHandling.Ignore, "{ \"arguments\": [\"2016-05-10T13:51:20+12:34\"], \"type\":1, \"target\": \"Method\" }"),
|
||||
new JsonProtocolTestData("InvocationMessage_IntegerArrayArgumentFirst", new InvocationMessage("Method", new object[] { 1, 2 }), false, NullValueHandling.Ignore, "{ \"arguments\": [1,2], \"type\":1, \"target\": \"Method\" }"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_IntegerArrayArgumentFirst", new StreamInvocationMessage("3", "Method", new object[] { 1, 2 }), false, NullValueHandling.Ignore, "{ \"type\":4, \"arguments\": [1,2], \"target\": \"Method\", \"invocationId\": \"3\" }"),
|
||||
new JsonProtocolTestData("CompletionMessage_ResultFirst", new CompletionMessage("15", null, 10, hasResult: true), false, NullValueHandling.Ignore, "{ \"type\":3, \"result\": 10, \"invocationId\": \"15\" }"),
|
||||
new JsonProtocolTestData("StreamItemMessage_ItemFirst", new StreamItemMessage("1a", "foo"), false, NullValueHandling.Ignore, "{ \"item\": \"foo\", \"invocationId\": \"1a\", \"type\":2 }")
|
||||
|
||||
}.ToDictionary(t => t.Name);
|
||||
|
||||
public static IEnumerable<object[]> OutOfOrderJsonTestDataNames => OutOfOrderJsonTestData.Keys.Select(name => new object[] { name });
|
||||
protected override IHubProtocol JsonHubProtocol => new JsonHubProtocol();
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ProtocolTestDataNames))]
|
||||
public void WriteMessage(string protocolTestDataName)
|
||||
[InlineData("", "Error reading JSON.")]
|
||||
[InlineData("42", "Unexpected JSON Token Type 'Number'. Expected a JSON Object.")]
|
||||
[InlineData("{\"type\":\"foo\"}", "Expected 'type' to be of type Number.")]
|
||||
public void CustomInvalidMessages(string input, string expectedMessage)
|
||||
{
|
||||
var testData = ProtocolTestData[protocolTestDataName];
|
||||
input = Frame(input);
|
||||
|
||||
var binder = new TestBinder(Array.Empty<Type>(), typeof(object));
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(input));
|
||||
var ex = Assert.Throws<InvalidDataException>(() => JsonHubProtocol.TryParseMessage(ref data, binder, out var _));
|
||||
Assert.Equal(expectedMessage, ex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(CustomProtocolTestDataNames))]
|
||||
public void CustomWriteMessage(string protocolTestDataName)
|
||||
{
|
||||
var testData = CustomProtocolTestData[protocolTestDataName];
|
||||
|
||||
var expectedOutput = Frame(testData.Json);
|
||||
|
||||
var protocolOptions = new NewtonsoftJsonHubProtocolOptions
|
||||
{
|
||||
PayloadSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
NullValueHandling = testData.NullValueHandling,
|
||||
ContractResolver = testData.CamelCase ? new CamelCasePropertyNamesContractResolver() : new DefaultContractResolver()
|
||||
}
|
||||
};
|
||||
|
||||
var protocol = new NewtonsoftJsonHubProtocol(Options.Create(protocolOptions));
|
||||
|
||||
var writer = MemoryBufferWriter.Get();
|
||||
try
|
||||
{
|
||||
protocol.WriteMessage(testData.Message, writer);
|
||||
JsonHubProtocol.WriteMessage(testData.Message, writer);
|
||||
var json = Encoding.UTF8.GetString(writer.ToArray());
|
||||
|
||||
Assert.Equal(expectedOutput, json);
|
||||
|
|
@ -145,205 +54,42 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ProtocolTestDataNames))]
|
||||
public void ParseMessage(string protocolTestDataName)
|
||||
[MemberData(nameof(CustomProtocolTestDataNames))]
|
||||
public void CustomParseMessage(string protocolTestDataName)
|
||||
{
|
||||
var testData = ProtocolTestData[protocolTestDataName];
|
||||
|
||||
var input = Frame(testData.Json);
|
||||
|
||||
var protocolOptions = new NewtonsoftJsonHubProtocolOptions
|
||||
{
|
||||
PayloadSerializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = testData.NullValueHandling,
|
||||
ContractResolver = testData.CamelCase ? new CamelCasePropertyNamesContractResolver() : new DefaultContractResolver()
|
||||
}
|
||||
};
|
||||
|
||||
var binder = new TestBinder(testData.Message);
|
||||
var protocol = new NewtonsoftJsonHubProtocol(Options.Create(protocolOptions));
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(input));
|
||||
protocol.TryParseMessage(ref data, binder, out var message);
|
||||
|
||||
Assert.Equal(testData.Message, message, TestHubMessageEqualityComparer.Instance);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", "Unexpected end when reading JSON.")]
|
||||
[InlineData("null", "Unexpected JSON Token Type 'Null'. Expected a JSON Object.")]
|
||||
[InlineData("42", "Unexpected JSON Token Type 'Integer'. Expected a JSON Object.")]
|
||||
[InlineData("'foo'", "Unexpected JSON Token Type 'String'. Expected a JSON Object.")]
|
||||
[InlineData("[42]", "Unexpected JSON Token Type 'Array'. Expected a JSON Object.")]
|
||||
[InlineData("{}", "Missing required property 'type'.")]
|
||||
|
||||
[InlineData("{'type':1,'headers':{\"Foo\": 42},'target':'test',arguments:[]}", "Expected header 'Foo' to be of type String.")]
|
||||
[InlineData("{'type':1,'headers':{\"Foo\": true},'target':'test',arguments:[]}", "Expected header 'Foo' to be of type String.")]
|
||||
[InlineData("{'type':1,'headers':{\"Foo\": null},'target':'test',arguments:[]}", "Expected header 'Foo' to be of type String.")]
|
||||
[InlineData("{'type':1,'headers':{\"Foo\": []},'target':'test',arguments:[]}", "Expected header 'Foo' to be of type String.")]
|
||||
|
||||
[InlineData("{'type':1}", "Missing required property 'target'.")]
|
||||
[InlineData("{'type':1,'invocationId':42}", "Expected 'invocationId' to be of type String.")]
|
||||
[InlineData("{'type':1,'invocationId':'42'}", "Missing required property 'target'.")]
|
||||
[InlineData("{'type':1,'invocationId':'42','target':42}", "Expected 'target' to be of type String.")]
|
||||
[InlineData("{'type':1,'invocationId':'42','target':'foo'}", "Missing required property 'arguments'.")]
|
||||
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':{}}", "Expected 'arguments' to be of type Array.")]
|
||||
|
||||
[InlineData("{'type':2}", "Missing required property 'invocationId'.")]
|
||||
[InlineData("{'type':2,'invocationId':42}", "Expected 'invocationId' to be of type String.")]
|
||||
[InlineData("{'type':2,'invocationId':'42'}", "Missing required property 'item'.")]
|
||||
|
||||
[InlineData("{'type':3}", "Missing required property 'invocationId'.")]
|
||||
[InlineData("{'type':3,'invocationId':42}", "Expected 'invocationId' to be of type String.")]
|
||||
[InlineData("{'type':3,'invocationId':'42','error':[]}", "Expected 'error' to be of type String.")]
|
||||
|
||||
[InlineData("{'type':4}", "Missing required property 'invocationId'.")]
|
||||
[InlineData("{'type':4,'invocationId':42}", "Expected 'invocationId' to be of type String.")]
|
||||
[InlineData("{'type':4,'invocationId':'42','target':42}", "Expected 'target' to be of type String.")]
|
||||
[InlineData("{'type':4,'invocationId':'42','target':'foo'}", "Missing required property 'arguments'.")]
|
||||
[InlineData("{'type':4,'invocationId':'42','target':'foo','arguments':{}}", "Expected 'arguments' to be of type Array.")]
|
||||
|
||||
[InlineData("{'type':'foo'}", "Expected 'type' to be of type Integer.")]
|
||||
|
||||
[InlineData("{'type':3,'invocationId':'42','error':'foo','result':true}", "The 'error' and 'result' properties are mutually exclusive.")]
|
||||
[InlineData("{'type':3,'invocationId':'42','result':true", "Unexpected end when reading JSON.")]
|
||||
public void InvalidMessages(string input, string expectedMessage)
|
||||
{
|
||||
input = Frame(input);
|
||||
|
||||
var binder = new TestBinder(Array.Empty<Type>(), typeof(object));
|
||||
var protocol = new NewtonsoftJsonHubProtocol();
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(input));
|
||||
var ex = Assert.Throws<InvalidDataException>(() => protocol.TryParseMessage(ref data, binder, out var _));
|
||||
Assert.Equal(expectedMessage, ex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(OutOfOrderJsonTestDataNames))]
|
||||
public void ParseOutOfOrderJson(string outOfOrderJsonTestDataName)
|
||||
{
|
||||
var testData = OutOfOrderJsonTestData[outOfOrderJsonTestDataName];
|
||||
var testData = CustomProtocolTestData[protocolTestDataName];
|
||||
|
||||
var input = Frame(testData.Json);
|
||||
|
||||
var binder = new TestBinder(testData.Message);
|
||||
var protocol = new NewtonsoftJsonHubProtocol();
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(input));
|
||||
protocol.TryParseMessage(ref data, binder, out var message);
|
||||
JsonHubProtocol.TryParseMessage(ref data, binder, out var message);
|
||||
|
||||
Assert.Equal(testData.Message, message, TestHubMessageEqualityComparer.Instance);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':[],'extraParameter':'1'}")]
|
||||
public void ExtraItemsInMessageAreIgnored(string input)
|
||||
[Fact(Skip = "Do we want types like Double to be cast to int automatically?")]
|
||||
public void MagicCast()
|
||||
{
|
||||
input = Frame(input);
|
||||
var input = Frame("{\"type\":1,\"target\":\"Method\",\"arguments\":[1.1]}");
|
||||
var expectedMessage = new InvocationMessage("Method", new object[] { 1 });
|
||||
|
||||
var binder = new TestBinder(paramTypes: new[] { typeof(int), typeof(string) }, returnType: typeof(bool));
|
||||
var protocol = new NewtonsoftJsonHubProtocol();
|
||||
var binder = new TestBinder(new[] { typeof(int) });
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(input));
|
||||
Assert.True(protocol.TryParseMessage(ref data, binder, out var message));
|
||||
Assert.NotNull(message);
|
||||
JsonHubProtocol.TryParseMessage(ref data, binder, out var message);
|
||||
|
||||
Assert.Equal(expectedMessage, message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':[]}", "Invocation provides 0 argument(s) but target expects 2.")]
|
||||
[InlineData("{'type':1,'arguments':[], 'invocationId':'42','target':'foo'}", "Invocation provides 0 argument(s) but target expects 2.")]
|
||||
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':[ 'abc', 'xyz']}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
|
||||
[InlineData("{'type':1,'invocationId':'42','arguments':[ 'abc', 'xyz'], 'target':'foo'}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
|
||||
[InlineData("{'type':4,'invocationId':'42','target':'foo','arguments':[]}", "Invocation provides 0 argument(s) but target expects 2.")]
|
||||
[InlineData("{'type':4,'invocationId':'42','target':'foo','arguments':[ 'abc', 'xyz']}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
|
||||
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':[1,'',{'1':1,'2':2}]}", "Invocation provides 3 argument(s) but target expects 2.")]
|
||||
[InlineData("{'type':1,'arguments':[1,'',{'1':1,'2':2}]},'invocationId':'42','target':'foo'", "Invocation provides 3 argument(s) but target expects 2.")]
|
||||
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':[1,[]]}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
|
||||
public void ArgumentBindingErrors(string input, string expectedMessage)
|
||||
public static IDictionary<string, JsonProtocolTestData> CustomProtocolTestData => new[]
|
||||
{
|
||||
input = Frame(input);
|
||||
new JsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", new object[] { 1, "Foo", 2.0f }), "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2]}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasFloatItem", new StreamItemMessage("123", 2.0f), "{\"type\":2,\"invocationId\":\"123\",\"item\":2}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasFloatResult", CompletionMessage.WithResult("123", 2.0f), "{\"type\":3,\"invocationId\":\"123\",\"result\":2}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20\\u002b12:34\"]}"),
|
||||
}.ToDictionary(t => t.Name);
|
||||
|
||||
var binder = new TestBinder(paramTypes: new[] { typeof(int), typeof(string) }, returnType: typeof(bool));
|
||||
var protocol = new NewtonsoftJsonHubProtocol();
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(input));
|
||||
protocol.TryParseMessage(ref data, binder, out var message);
|
||||
var bindingFailure = Assert.IsType<InvocationBindingFailureMessage>(message);
|
||||
Assert.Equal(expectedMessage, bindingFailure.BindingFailure.SourceException.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':['2007-03-01T13:00:00Z']}")]
|
||||
[InlineData("{'type':1,'invocationId':'42','arguments':['2007-03-01T13:00:00Z'],'target':'foo'}")]
|
||||
public void DateTimeArgumentPreservesUtcKind(string input)
|
||||
{
|
||||
var binder = new TestBinder(new[] { typeof(DateTime) });
|
||||
var protocol = new NewtonsoftJsonHubProtocol();
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(Frame(input)));
|
||||
protocol.TryParseMessage(ref data, binder, out var message);
|
||||
var invocationMessage = Assert.IsType<InvocationMessage>(message);
|
||||
|
||||
Assert.Single(invocationMessage.Arguments);
|
||||
var dt = Assert.IsType<DateTime>(invocationMessage.Arguments[0]);
|
||||
Assert.Equal(DateTimeKind.Utc, dt.Kind);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{'type':3,'invocationId':'42','target':'foo','arguments':[],'result':'2007-03-01T13:00:00Z'}")]
|
||||
[InlineData("{'type':3,'target':'foo','arguments':[],'result':'2007-03-01T13:00:00Z','invocationId':'42'}")]
|
||||
public void DateTimeReturnValuePreservesUtcKind(string input)
|
||||
{
|
||||
var binder = new TestBinder(typeof(DateTime));
|
||||
var protocol = new NewtonsoftJsonHubProtocol();
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(Frame(input)));
|
||||
protocol.TryParseMessage(ref data, binder, out var message);
|
||||
var invocationMessage = Assert.IsType<CompletionMessage>(message);
|
||||
|
||||
var dt = Assert.IsType<DateTime>(invocationMessage.Result);
|
||||
Assert.Equal(DateTimeKind.Utc, dt.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadToEndOfArgumentArrayOnError()
|
||||
{
|
||||
var binder = new TestBinder(new[] { typeof(string) });
|
||||
var protocol = new NewtonsoftJsonHubProtocol();
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(Frame("{'type':1,'invocationId':'42','target':'foo','arguments':[[],{'target':'foo2'}]}")));
|
||||
protocol.TryParseMessage(ref data, binder, out var message);
|
||||
var bindingFailure = Assert.IsType<InvocationBindingFailureMessage>(message);
|
||||
|
||||
Assert.Equal("foo", bindingFailure.Target);
|
||||
}
|
||||
|
||||
private static string Frame(string input)
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes(input);
|
||||
return Encoding.UTF8.GetString(FormatMessageToArray(data));
|
||||
}
|
||||
|
||||
private static byte[] FormatMessageToArray(byte[] message)
|
||||
{
|
||||
var output = new MemoryStream();
|
||||
output.Write(message, 0, message.Length);
|
||||
output.WriteByte(TextMessageFormatter.RecordSeparator);
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
public class JsonProtocolTestData
|
||||
{
|
||||
public string Name { get; }
|
||||
public HubMessage Message { get; }
|
||||
public bool CamelCase { get; }
|
||||
public NullValueHandling NullValueHandling { get; }
|
||||
public string Json { get; }
|
||||
|
||||
public JsonProtocolTestData(string name, HubMessage message, bool camelCase, NullValueHandling nullValueHandling, string json)
|
||||
{
|
||||
Name = name;
|
||||
Message = message;
|
||||
CamelCase = camelCase;
|
||||
NullValueHandling = nullValueHandling;
|
||||
Json = json;
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
public static IEnumerable<object[]> CustomProtocolTestDataNames => CustomProtocolTestData.Keys.Select(name => new object[] { name });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,291 @@
|
|||
// 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.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
|
||||
{
|
||||
using static HubMessageHelpers;
|
||||
|
||||
public abstract class JsonHubProtocolTestsBase
|
||||
{
|
||||
protected abstract IHubProtocol JsonHubProtocol { get; }
|
||||
|
||||
public static readonly IDictionary<string, string> TestHeaders = new Dictionary<string, string>
|
||||
{
|
||||
{ "Foo", "Bar" },
|
||||
{ "KeyWith\nNew\r\nLines", "Still Works" },
|
||||
{ "ValueWithNewLines", "Also\nWorks\r\nFine" },
|
||||
};
|
||||
|
||||
// It's cleaner to do this as a prefix and use concatenation rather than string interpolation because JSON is already filled with '{'s.
|
||||
public static readonly string SerializedHeaders = "\"headers\":{\"Foo\":\"Bar\",\"KeyWith\\nNew\\r\\nLines\":\"Still Works\",\"ValueWithNewLines\":\"Also\\nWorks\\r\\nFine\"}";
|
||||
|
||||
public static IDictionary<string, JsonProtocolTestData> ProtocolTestData => new[]
|
||||
{
|
||||
new JsonProtocolTestData("InvocationMessage_HasInvocationId", new InvocationMessage("123", "Target", new object[] { 1, "Foo" }), "{\"type\":1,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\"]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasBoolArgument", new InvocationMessage(null, "Target", new object[] { true }), "{\"type\":1,\"target\":\"Target\",\"arguments\":[true]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasNullArgument", new InvocationMessage(null, "Target", new object[] { null }), "{\"type\":1,\"target\":\"Target\",\"arguments\":[null]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasStreamArgument", new InvocationMessage(null, "Target", Array.Empty<object>(), new string[] { "__test_id__" }), "{\"type\":1,\"target\":\"Target\",\"arguments\":[],\"streamIds\":[\"__test_id__\"]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasStreamAndNormalArgument", new InvocationMessage(null, "Target", new object[] { 42 }, new string[] { "__test_id__" }), "{\"type\":1,\"target\":\"Target\",\"arguments\":[42],\"streamIds\":[\"__test_id__\"]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_HasMultipleStreams", new InvocationMessage(null, "Target", Array.Empty<object>(), new string[] { "__test_id__", "__test_id2__" }), "{\"type\":1,\"target\":\"Target\",\"arguments\":[],\"streamIds\":[\"__test_id__\",\"__test_id2__\"]}"),
|
||||
new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgument", new InvocationMessage("Method", new object[] { DateTimeOffset.Parse("2016-05-10T13:51:20+12:34") }), "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"),
|
||||
|
||||
new JsonProtocolTestData("StreamItemMessage_HasIntegerItem", new StreamItemMessage("123", 1), "{\"type\":2,\"invocationId\":\"123\",\"item\":1}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasStringItem", new StreamItemMessage("123", "Foo"), "{\"type\":2,\"invocationId\":\"123\",\"item\":\"Foo\"}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasBoolItem", new StreamItemMessage("123", true), "{\"type\":2,\"invocationId\":\"123\",\"item\":true}"),
|
||||
new JsonProtocolTestData("StreamItemMessage_HasNullItem", new StreamItemMessage("123", null), "{\"type\":2,\"invocationId\":\"123\",\"item\":null}"),
|
||||
|
||||
// Dictionary not supported yet
|
||||
//new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new CustomObject())), "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":[1,2,3]}}"),
|
||||
//new JsonProtocolTestData("InvocationMessage_HasHeaders", AddHeaders(TestHeaders, new InvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f })), "{\"type\":1," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"),
|
||||
//new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() })), "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":[1,2,3]}]}"),
|
||||
//new JsonProtocolTestData("CancelInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new CancelInvocationMessage("123")), "{\"type\":5," + SerializedHeaders + ",\"invocationId\":\"123\"}"),
|
||||
|
||||
new JsonProtocolTestData("CompletionMessage_HasIntegerResult", CompletionMessage.WithResult("123", 1), "{\"type\":3,\"invocationId\":\"123\",\"result\":1}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasStringResult", CompletionMessage.WithResult("123", "Foo"), "{\"type\":3,\"invocationId\":\"123\",\"result\":\"Foo\"}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasBoolResult", CompletionMessage.WithResult("123", true), "{\"type\":3,\"invocationId\":\"123\",\"result\":true}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasNullResult", CompletionMessage.WithResult("123", null), "{\"type\":3,\"invocationId\":\"123\",\"result\":null}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasError", CompletionMessage.WithError("123", "Whoops!"), "{\"type\":3,\"invocationId\":\"123\",\"error\":\"Whoops!\"}"),
|
||||
new JsonProtocolTestData("CompletionMessage_HasErrorAndHeaders", AddHeaders(TestHeaders, CompletionMessage.WithError("123", "Whoops!")), "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"error\":\"Whoops!\"}"),
|
||||
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasInvocationId", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo" }), "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\"]}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasBoolArgument", new StreamInvocationMessage("123", "Target", new object[] { true }), "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[true]}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasNullArgument", new StreamInvocationMessage("123", "Target", new object[] { null }), "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[null]}"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_HasStreamArgument", new StreamInvocationMessage("123", "Target", Array.Empty<object>(), new string[] { "__test_id__" }), "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[],\"streamIds\":[\"__test_id__\"]}"),
|
||||
|
||||
new JsonProtocolTestData("CancelInvocationMessage_HasInvocationId", new CancelInvocationMessage("123"), "{\"type\":5,\"invocationId\":\"123\"}"),
|
||||
|
||||
new JsonProtocolTestData("PingMessage", PingMessage.Instance, "{\"type\":6}"),
|
||||
|
||||
new JsonProtocolTestData("CloseMessage", CloseMessage.Empty, "{\"type\":7}"),
|
||||
new JsonProtocolTestData("CloseMessage_HasError", new CloseMessage("Error!"), "{\"type\":7,\"error\":\"Error!\"}"),
|
||||
new JsonProtocolTestData("CloseMessage_HasErrorEmptyString", new CloseMessage(""), "{\"type\":7,\"error\":\"\"}"),
|
||||
|
||||
}.ToDictionary(t => t.Name);
|
||||
|
||||
public static IEnumerable<object[]> ProtocolTestDataNames => ProtocolTestData.Keys.Select(name => new object[] { name });
|
||||
|
||||
public static IDictionary<string, JsonProtocolTestData> OutOfOrderJsonTestData => new[]
|
||||
{
|
||||
new JsonProtocolTestData("InvocationMessage_StringIsoDateArgumentFirst", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), "{ \"arguments\": [\"2016-05-10T13:51:20+12:34\"], \"type\":1, \"target\": \"Method\" }"),
|
||||
//new JsonProtocolTestData("InvocationMessage_StringIsoDateArgumentFirst", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), false, "{ \"arguments\": [\"2016-05-10T13:51:20+12:34\"], \"type\":1, \"target\": \"Method\" }"),
|
||||
//new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgumentFirst", new InvocationMessage("Method", new object[] { DateTimeOffset.Parse("2016-05-10T13:51:20+12:34") }), false, "{ \"arguments\": [\"2016-05-10T13:51:20+12:34\"], \"type\":1, \"target\": \"Method\" }"),
|
||||
new JsonProtocolTestData("InvocationMessage_IntegerArrayArgumentFirst", new InvocationMessage("Method", new object[] { 1, 2 }), "{ \"arguments\": [1,2], \"type\":1, \"target\": \"Method\" }"),
|
||||
new JsonProtocolTestData("StreamInvocationMessage_IntegerArrayArgumentFirst", new StreamInvocationMessage("3", "Method", new object[] { 1, 2 }), "{ \"type\":4, \"arguments\": [1,2], \"target\": \"Method\", \"invocationId\": \"3\" }"),
|
||||
new JsonProtocolTestData("CompletionMessage_ResultFirst", new CompletionMessage("15", null, 10, hasResult: true), "{ \"type\":3, \"result\": 10, \"invocationId\": \"15\" }"),
|
||||
new JsonProtocolTestData("StreamItemMessage_ItemFirst", new StreamItemMessage("1a", "foo"), "{ \"item\": \"foo\", \"invocationId\": \"1a\", \"type\":2 }")
|
||||
|
||||
}.ToDictionary(t => t.Name);
|
||||
|
||||
public static IEnumerable<object[]> OutOfOrderJsonTestDataNames => OutOfOrderJsonTestData.Keys.Select(name => new object[] { name });
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ProtocolTestDataNames))]
|
||||
public void WriteMessage(string protocolTestDataName)
|
||||
{
|
||||
var testData = ProtocolTestData[protocolTestDataName];
|
||||
|
||||
var expectedOutput = Frame(testData.Json);
|
||||
|
||||
var writer = MemoryBufferWriter.Get();
|
||||
try
|
||||
{
|
||||
JsonHubProtocol.WriteMessage(testData.Message, writer);
|
||||
var json = Encoding.UTF8.GetString(writer.ToArray());
|
||||
|
||||
Assert.Equal(expectedOutput, json);
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemoryBufferWriter.Return(writer);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ProtocolTestDataNames))]
|
||||
public void ParseMessage(string protocolTestDataName)
|
||||
{
|
||||
var testData = ProtocolTestData[protocolTestDataName];
|
||||
|
||||
var input = Frame(testData.Json);
|
||||
|
||||
var binder = new TestBinder(testData.Message);
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(input));
|
||||
JsonHubProtocol.TryParseMessage(ref data, binder, out var message);
|
||||
|
||||
Assert.Equal(testData.Message, message, TestHubMessageEqualityComparer.Instance);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("null", "Unexpected JSON Token Type 'Null'. Expected a JSON Object.")]
|
||||
[InlineData("\"foo\"", "Unexpected JSON Token Type 'String'. Expected a JSON Object.")]
|
||||
[InlineData("[42]", "Unexpected JSON Token Type 'Array'. Expected a JSON Object.")]
|
||||
[InlineData("{}", "Missing required property 'type'.")]
|
||||
|
||||
[InlineData("{\"type\":1,\"headers\":{\"Foo\": 42},\"target\":\"test\",arguments:[]}", "Expected header 'Foo' to be of type String.")]
|
||||
[InlineData("{\"type\":1,\"headers\":{\"Foo\": true},\"target\":\"test\",arguments:[]}", "Expected header 'Foo' to be of type String.")]
|
||||
[InlineData("{\"type\":1,\"headers\":{\"Foo\": null},\"target\":\"test\",arguments:[]}", "Expected header 'Foo' to be of type String.")]
|
||||
[InlineData("{\"type\":1,\"headers\":{\"Foo\": []},\"target\":\"test\",arguments:[]}", "Expected header 'Foo' to be of type String.")]
|
||||
|
||||
[InlineData("{\"type\":1}", "Missing required property 'target'.")]
|
||||
[InlineData("{\"type\":1,\"invocationId\":42}", "Expected 'invocationId' to be of type String.")]
|
||||
[InlineData("{\"type\":1,\"invocationId\":\"42\"}", "Missing required property 'target'.")]
|
||||
[InlineData("{\"type\":1,\"invocationId\":\"42\",\"target\":42}", "Expected 'target' to be of type String.")]
|
||||
[InlineData("{\"type\":1,\"invocationId\":\"42\",\"target\":\"foo\"}", "Missing required property 'arguments'.")]
|
||||
[InlineData("{\"type\":1,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":{}}", "Expected 'arguments' to be of type Array.")]
|
||||
|
||||
[InlineData("{\"type\":2}", "Missing required property 'invocationId'.")]
|
||||
[InlineData("{\"type\":2,\"invocationId\":42}", "Expected 'invocationId' to be of type String.")]
|
||||
[InlineData("{\"type\":2,\"invocationId\":\"42\"}", "Missing required property 'item'.")]
|
||||
|
||||
[InlineData("{\"type\":3}", "Missing required property 'invocationId'.")]
|
||||
[InlineData("{\"type\":3,\"invocationId\":42}", "Expected 'invocationId' to be of type String.")]
|
||||
[InlineData("{\"type\":3,\"invocationId\":\"42\",\"error\":[]}", "Expected 'error' to be of type String.")]
|
||||
|
||||
[InlineData("{\"type\":4}", "Missing required property 'invocationId'.")]
|
||||
[InlineData("{\"type\":4,\"invocationId\":42}", "Expected 'invocationId' to be of type String.")]
|
||||
[InlineData("{\"type\":4,\"invocationId\":\"42\",\"target\":42}", "Expected 'target' to be of type String.")]
|
||||
[InlineData("{\"type\":4,\"invocationId\":\"42\",\"target\":\"foo\"}", "Missing required property 'arguments'.")]
|
||||
[InlineData("{\"type\":4,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":{}}", "Expected 'arguments' to be of type Array.")]
|
||||
|
||||
//[InlineData("{\"type\":3,\"invocationId\":\"42\",\"error\":\"foo\",\"result\":true}", "The 'error' and 'result' properties are mutually exclusive.")]
|
||||
//[InlineData("{\"type\":3,\"invocationId\":\"42\",\"result\":true", "Unexpected end when reading JSON.")]
|
||||
public void InvalidMessages(string input, string expectedMessage)
|
||||
{
|
||||
input = Frame(input);
|
||||
|
||||
var binder = new TestBinder(Array.Empty<Type>(), typeof(object));
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(input));
|
||||
var ex = Assert.Throws<InvalidDataException>(() => JsonHubProtocol.TryParseMessage(ref data, binder, out var _));
|
||||
Assert.Equal(expectedMessage, ex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(OutOfOrderJsonTestDataNames))]
|
||||
public void ParseOutOfOrderJson(string outOfOrderJsonTestDataName)
|
||||
{
|
||||
var testData = OutOfOrderJsonTestData[outOfOrderJsonTestDataName];
|
||||
|
||||
var input = Frame(testData.Json);
|
||||
|
||||
var binder = new TestBinder(testData.Message);
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(input));
|
||||
JsonHubProtocol.TryParseMessage(ref data, binder, out var message);
|
||||
|
||||
Assert.Equal(testData.Message, message, TestHubMessageEqualityComparer.Instance);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{\"type\":1,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":[],\"extraParameter\":\"1\"}")]
|
||||
public void ExtraItemsInMessageAreIgnored(string input)
|
||||
{
|
||||
input = Frame(input);
|
||||
|
||||
var binder = new TestBinder(paramTypes: new[] { typeof(int), typeof(string) }, returnType: typeof(bool));
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(input));
|
||||
Assert.True(JsonHubProtocol.TryParseMessage(ref data, binder, out var message));
|
||||
Assert.NotNull(message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{\"type\":1,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":[]}", "Invocation provides 0 argument(s) but target expects 2.")]
|
||||
[InlineData("{\"type\":1,\"arguments\":[], \"invocationId\":\"42\",\"target\":\"foo\"}", "Invocation provides 0 argument(s) but target expects 2.")]
|
||||
[InlineData("{\"type\":1,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":[ \"abc\", \"xyz\"]}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
|
||||
[InlineData("{\"type\":1,\"invocationId\":\"42\",\"arguments\":[ \"abc\", \"xyz\"], \"target\":\"foo\"}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
|
||||
[InlineData("{\"type\":4,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":[]}", "Invocation provides 0 argument(s) but target expects 2.")]
|
||||
[InlineData("{\"type\":4,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":[ \"abc\", \"xyz\"]}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
|
||||
[InlineData("{\"type\":1,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":[1,\"\",{\"1\":1,\"2\":2}]}", "Invocation provides 3 argument(s) but target expects 2.")]
|
||||
[InlineData("{\"type\":1,\"arguments\":[1,\"\",{\"1\":1,\"2\":2}]},\"invocationId\":\"42\",\"target\":\"foo\"", "Invocation provides 3 argument(s) but target expects 2.")]
|
||||
[InlineData("{\"type\":1,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":[1,[1]]}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
|
||||
// [InlineData("{\"type\":1,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":[1,[]]}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
|
||||
public void ArgumentBindingErrors(string input, string expectedMessage)
|
||||
{
|
||||
input = Frame(input);
|
||||
|
||||
var binder = new TestBinder(paramTypes: new[] { typeof(int), typeof(string) }, returnType: typeof(bool));
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(input));
|
||||
JsonHubProtocol.TryParseMessage(ref data, binder, out var message);
|
||||
var bindingFailure = Assert.IsType<InvocationBindingFailureMessage>(message);
|
||||
Assert.Equal(expectedMessage, bindingFailure.BindingFailure.SourceException.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{\"type\":1,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":[\"2007-03-01T13:00:00Z\"]}")]
|
||||
[InlineData("{\"type\":1,\"invocationId\":\"42\",\"arguments\":[\"2007-03-01T13:00:00Z\"],\"target\":\"foo\"}")]
|
||||
public void DateTimeArgumentPreservesUtcKind(string input)
|
||||
{
|
||||
var binder = new TestBinder(new[] { typeof(DateTime) });
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(Frame(input)));
|
||||
JsonHubProtocol.TryParseMessage(ref data, binder, out var message);
|
||||
var invocationMessage = Assert.IsType<InvocationMessage>(message);
|
||||
|
||||
Assert.Single(invocationMessage.Arguments);
|
||||
var dt = Assert.IsType<DateTime>(invocationMessage.Arguments[0]);
|
||||
Assert.Equal(DateTimeKind.Utc, dt.Kind);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{\"type\":3,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":[],\"result\":\"2007-03-01T13:00:00Z\"}")]
|
||||
[InlineData("{\"type\":3,\"target\":\"foo\",\"arguments\":[],\"result\":\"2007-03-01T13:00:00Z\",\"invocationId\":\"42\"}")]
|
||||
public void DateTimeReturnValuePreservesUtcKind(string input)
|
||||
{
|
||||
var binder = new TestBinder(typeof(DateTime));
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(Frame(input)));
|
||||
JsonHubProtocol.TryParseMessage(ref data, binder, out var message);
|
||||
var invocationMessage = Assert.IsType<CompletionMessage>(message);
|
||||
|
||||
var dt = Assert.IsType<DateTime>(invocationMessage.Result);
|
||||
Assert.Equal(DateTimeKind.Utc, dt.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadToEndOfArgumentArrayOnError()
|
||||
{
|
||||
var binder = new TestBinder(new[] { typeof(string) });
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(Frame("{\"type\":1,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":[[],{\"target\":\"foo2\"}]}")));
|
||||
JsonHubProtocol.TryParseMessage(ref data, binder, out var message);
|
||||
var bindingFailure = Assert.IsType<InvocationBindingFailureMessage>(message);
|
||||
|
||||
Assert.Equal("foo", bindingFailure.Target);
|
||||
}
|
||||
|
||||
public static string Frame(string input)
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes(input);
|
||||
return Encoding.UTF8.GetString(FormatMessageToArray(data));
|
||||
}
|
||||
|
||||
private static byte[] FormatMessageToArray(byte[] message)
|
||||
{
|
||||
var output = new MemoryStream();
|
||||
output.Write(message, 0, message.Length);
|
||||
output.WriteByte(TextMessageFormatter.RecordSeparator);
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
public class JsonProtocolTestData
|
||||
{
|
||||
public string Name { get; }
|
||||
public HubMessage Message { get; }
|
||||
public string Json { get; }
|
||||
|
||||
public JsonProtocolTestData(string name, HubMessage message, string json)
|
||||
{
|
||||
Name = name;
|
||||
Message = message;
|
||||
Json = json;
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
// 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.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
|
||||
{
|
||||
using static HubMessageHelpers;
|
||||
|
||||
public class NewtonsoftJsonHubProtocolTests : JsonHubProtocolTestsBase
|
||||
{
|
||||
protected override IHubProtocol JsonHubProtocol => new NewtonsoftJsonHubProtocol();
|
||||
|
||||
[Theory]
|
||||
[InlineData("", "Unexpected end when reading JSON.")]
|
||||
[InlineData("42", "Unexpected JSON Token Type 'Integer'. Expected a JSON Object.")]
|
||||
[InlineData("{\"type\":\"foo\"}", "Expected 'type' to be of type Integer.")]
|
||||
public void CustomInvalidMessages(string input, string expectedMessage)
|
||||
{
|
||||
input = Frame(input);
|
||||
|
||||
var binder = new TestBinder(Array.Empty<Type>(), typeof(object));
|
||||
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(input));
|
||||
var ex = Assert.Throws<InvalidDataException>(() => JsonHubProtocol.TryParseMessage(ref data, binder, out var _));
|
||||
Assert.Equal(expectedMessage, ex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(CustomProtocolTestDataNames))]
|
||||
public void CustomWriteMessage(string protocolTestDataName)
|
||||
{
|
||||
var testData = CustomProtocolTestData[protocolTestDataName];
|
||||
|
||||
var expectedOutput = Frame(testData.Json);
|
||||
|
||||
var protocolOptions = new NewtonsoftJsonHubProtocolOptions
|
||||
{
|
||||
PayloadSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
NullValueHandling = testData.NullValueHandling,
|
||||
ContractResolver = testData.CamelCase ? new CamelCasePropertyNamesContractResolver() : new DefaultContractResolver()
|
||||
}
|
||||
};
|
||||
|
||||
var protocol = new NewtonsoftJsonHubProtocol(Options.Create(protocolOptions));
|
||||
|
||||
var writer = MemoryBufferWriter.Get();
|
||||
try
|
||||
{
|
||||
protocol.WriteMessage(testData.Message, writer);
|
||||
var json = Encoding.UTF8.GetString(writer.ToArray());
|
||||
|
||||
Assert.Equal(expectedOutput, json);
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemoryBufferWriter.Return(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public static IDictionary<string, NewtonsoftJsonProtocolTestData> CustomProtocolTestData => new[]
|
||||
{
|
||||
new NewtonsoftJsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"),
|
||||
new NewtonsoftJsonProtocolTestData("StreamItemMessage_HasFloatItem", new StreamItemMessage("123", 2.0f), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":2.0}"),
|
||||
new NewtonsoftJsonProtocolTestData("CompletionMessage_HasFloatResult", CompletionMessage.WithResult("123", 2.0f), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":2.0}"),
|
||||
new NewtonsoftJsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"),
|
||||
new NewtonsoftJsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), false, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"),
|
||||
new NewtonsoftJsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"),
|
||||
new NewtonsoftJsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"),
|
||||
new NewtonsoftJsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, NullValueHandling.Include, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"),
|
||||
new NewtonsoftJsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, NullValueHandling.Include, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"),
|
||||
new NewtonsoftJsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"),
|
||||
new NewtonsoftJsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new CustomObject()), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"),
|
||||
new NewtonsoftJsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, NullValueHandling.Include, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"),
|
||||
new NewtonsoftJsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new CustomObject()), true, NullValueHandling.Include, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"),
|
||||
new NewtonsoftJsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new CustomObject())), true, NullValueHandling.Include, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"),
|
||||
new NewtonsoftJsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"),
|
||||
new NewtonsoftJsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new CustomObject()), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"),
|
||||
new NewtonsoftJsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, NullValueHandling.Include, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"),
|
||||
new NewtonsoftJsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new CustomObject()), true, NullValueHandling.Include, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"),
|
||||
new NewtonsoftJsonProtocolTestData("CompletionMessage_HasTestHeadersAndCustomItemResult", AddHeaders(TestHeaders, CompletionMessage.WithResult("123", new CustomObject())), true, NullValueHandling.Include, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"),
|
||||
new NewtonsoftJsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\"}"),
|
||||
new NewtonsoftJsonProtocolTestData("CompletionMessage_HasErrorAndHeadersAndCamelCase", AddHeaders(TestHeaders, CompletionMessage.Empty("123")), true, NullValueHandling.Ignore, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\"}"),
|
||||
new NewtonsoftJsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"),
|
||||
new NewtonsoftJsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"),
|
||||
new NewtonsoftJsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, NullValueHandling.Include, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"),
|
||||
new NewtonsoftJsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, NullValueHandling.Include, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"),
|
||||
new NewtonsoftJsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() })), true, NullValueHandling.Include, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"),
|
||||
new NewtonsoftJsonProtocolTestData("CloseMessage_HasErrorWithCamelCase", new CloseMessage("Error!"), true, NullValueHandling.Ignore, "{\"type\":7,\"error\":\"Error!\"}"),
|
||||
}.ToDictionary(t => t.Name);
|
||||
|
||||
public static IEnumerable<object[]> CustomProtocolTestDataNames => CustomProtocolTestData.Keys.Select(name => new object[] { name });
|
||||
|
||||
public class NewtonsoftJsonProtocolTestData : JsonProtocolTestData
|
||||
{
|
||||
public NewtonsoftJsonProtocolTestData(string name, HubMessage message, bool camelCase, NullValueHandling nullValueHandling, string json) : base(name, message, json)
|
||||
{
|
||||
CamelCase = camelCase;
|
||||
NullValueHandling = nullValueHandling;
|
||||
}
|
||||
|
||||
public bool CamelCase { get; }
|
||||
public NullValueHandling NullValueHandling { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Common" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Protocols.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
<Reference Include="Microsoft.AspNetCore.SignalR.Common" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Core" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" />
|
||||
<Reference Include="Microsoft.AspNetCore.Testing" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Testing" />
|
||||
<Reference Include="Microsoft.Extensions.ValueStopwatch.Sources" />
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
|
|||
[Params(Message.NoArguments, Message.FewArguments, Message.ManyArguments, Message.LargeArguments)]
|
||||
public Message Input { get; set; }
|
||||
|
||||
[Params(Protocol.MsgPack, Protocol.Json)]
|
||||
[Params(Protocol.MsgPack, Protocol.Json, Protocol.NewtonsoftJson)]
|
||||
public Protocol HubProtocol { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
|
|
@ -30,6 +30,9 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
|
|||
_hubProtocol = new MessagePackHubProtocol();
|
||||
break;
|
||||
case Protocol.Json:
|
||||
_hubProtocol = new JsonHubProtocol();
|
||||
break;
|
||||
case Protocol.NewtonsoftJson:
|
||||
_hubProtocol = new NewtonsoftJsonHubProtocol();
|
||||
break;
|
||||
}
|
||||
|
|
@ -77,7 +80,8 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
|
|||
public enum Protocol
|
||||
{
|
||||
MsgPack = 0,
|
||||
Json = 1
|
||||
Json = 1,
|
||||
NewtonsoftJson = 2,
|
||||
}
|
||||
|
||||
public enum Message
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
<Reference Include="Microsoft.AspNetCore.SignalR.Common" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Core" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Protocols.Json" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" />
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<Reference Include="Microsoft.Extensions.ValueStopwatch.Sources" />
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ namespace BenchmarkServer
|
|||
{
|
||||
o.EnableDetailedErrors = true;
|
||||
})
|
||||
// TODO: Json vs NewtonsoftJson option
|
||||
.AddMessagePackProtocol();
|
||||
|
||||
var redisConnectionString = _config["SignalRRedis"];
|
||||
|
|
|
|||
|
|
@ -12,16 +12,16 @@ namespace SignalRSamples.Hubs
|
|||
{
|
||||
public class Streaming : Hub
|
||||
{
|
||||
public async IAsyncEnumerable<int> AsyncEnumerableCounter(int count, int delay)
|
||||
public async IAsyncEnumerable<int> AsyncEnumerableCounter(int count, double delay)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
yield return i;
|
||||
await Task.Delay(delay);
|
||||
await Task.Delay((int)delay);
|
||||
}
|
||||
}
|
||||
|
||||
public ChannelReader<int> ObservableCounter(int count, int delay)
|
||||
public ChannelReader<int> ObservableCounter(int count, double delay)
|
||||
{
|
||||
var observable = Observable.Interval(TimeSpan.FromMilliseconds(delay))
|
||||
.Select((_, index) => index)
|
||||
|
|
|
|||
|
|
@ -23,16 +23,12 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
|||
_logger = logger ?? NullLogger<DefaultHubProtocolResolver>.Instance;
|
||||
_availableProtocols = new Dictionary<string, IHubProtocol>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// We might get duplicates in _hubProtocols, but we're going to check it and throw in just a sec.
|
||||
// We might get duplicates in _hubProtocols, but we're going to check it and overwrite in just a sec.
|
||||
_hubProtocols = availableProtocols.ToList();
|
||||
foreach (var protocol in _hubProtocols)
|
||||
{
|
||||
if (_availableProtocols.ContainsKey(protocol.Name))
|
||||
{
|
||||
throw new InvalidOperationException($"Multiple Hub Protocols with the name '{protocol.Name}' were registered.");
|
||||
}
|
||||
Log.RegisteredSignalRProtocol(_logger, protocol.Name, protocol.GetType());
|
||||
_availableProtocols.Add(protocol.Name, protocol);
|
||||
_availableProtocols[protocol.Name] = protocol;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return Clients.User(userId).SendAsync("Send", message);
|
||||
}
|
||||
|
||||
public Task SendToMultipleUsers(IReadOnlyList<string> userIds, string message)
|
||||
public Task SendToMultipleUsers(List<string> userIds, string message)
|
||||
{
|
||||
return Clients.Users(userIds).SendAsync("Send", message);
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return Clients.Client(connectionId).SendAsync("Send", message);
|
||||
}
|
||||
|
||||
public Task SendToMultipleClients(string message, IReadOnlyList<string> connectionIds)
|
||||
public Task SendToMultipleClients(string message, List<string> connectionIds)
|
||||
{
|
||||
return Clients.Clients(connectionIds).SendAsync("Send", message);
|
||||
}
|
||||
|
|
@ -48,12 +48,12 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return Clients.Group(groupName).SendAsync("Send", message);
|
||||
}
|
||||
|
||||
public Task GroupExceptSendMethod(string groupName, string message, IReadOnlyList<string> excludedConnectionIds)
|
||||
public Task GroupExceptSendMethod(string groupName, string message, List<string> excludedConnectionIds)
|
||||
{
|
||||
return Clients.GroupExcept(groupName, excludedConnectionIds).SendAsync("Send", message);
|
||||
}
|
||||
|
||||
public Task SendToMultipleGroups(string message, IReadOnlyList<string> groupNames)
|
||||
public Task SendToMultipleGroups(string message, List<string> groupNames)
|
||||
{
|
||||
return Clients.Groups(groupNames).SendAsync("Send", message);
|
||||
}
|
||||
|
|
@ -142,7 +142,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
{
|
||||
}
|
||||
|
||||
public Task SendToAllExcept(string message, IReadOnlyList<string> excludedConnectionIds)
|
||||
public Task SendToAllExcept(string message, List<string> excludedConnectionIds)
|
||||
{
|
||||
return Clients.AllExcept(excludedConnectionIds).SendAsync("Send", message);
|
||||
}
|
||||
|
|
@ -303,7 +303,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return Clients.User(userId).Send(message);
|
||||
}
|
||||
|
||||
public Task SendToMultipleUsers(IReadOnlyList<string> userIds, string message)
|
||||
public Task SendToMultipleUsers(List<string> userIds, string message)
|
||||
{
|
||||
return Clients.Users(userIds).Send(message);
|
||||
}
|
||||
|
|
@ -313,7 +313,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return Clients.Client(connectionId).Send(message);
|
||||
}
|
||||
|
||||
public Task SendToMultipleClients(string message, IReadOnlyList<string> connectionIds)
|
||||
public Task SendToMultipleClients(string message, List<string> connectionIds)
|
||||
{
|
||||
return Clients.Clients(connectionIds).Send(message);
|
||||
}
|
||||
|
|
@ -328,7 +328,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return Clients.Group(groupName).Send(message);
|
||||
}
|
||||
|
||||
public Task GroupExceptSendMethod(string groupName, string message, IReadOnlyList<string> excludedConnectionIds)
|
||||
public Task GroupExceptSendMethod(string groupName, string message, List<string> excludedConnectionIds)
|
||||
{
|
||||
return Clients.GroupExcept(groupName, excludedConnectionIds).Send(message);
|
||||
}
|
||||
|
|
@ -338,7 +338,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return Clients.OthersInGroup(groupName).Send(message);
|
||||
}
|
||||
|
||||
public Task SendToMultipleGroups(string message, IReadOnlyList<string> groupNames)
|
||||
public Task SendToMultipleGroups(string message, List<string> groupNames)
|
||||
{
|
||||
return Clients.Groups(groupNames).Send(message);
|
||||
}
|
||||
|
|
@ -348,7 +348,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return Clients.All.Broadcast(message);
|
||||
}
|
||||
|
||||
public Task SendToAllExcept(string message, IReadOnlyList<string> excludedConnectionIds)
|
||||
public Task SendToAllExcept(string message, List<string> excludedConnectionIds)
|
||||
{
|
||||
return Clients.AllExcept(excludedConnectionIds).Send(message);
|
||||
}
|
||||
|
|
@ -383,7 +383,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return Clients.User(userId).Send(message);
|
||||
}
|
||||
|
||||
public Task SendToMultipleUsers(IReadOnlyList<string> userIds, string message)
|
||||
public Task SendToMultipleUsers(List<string> userIds, string message)
|
||||
{
|
||||
return Clients.Users(userIds).Send(message);
|
||||
}
|
||||
|
|
@ -393,7 +393,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return Clients.Client(connectionId).Send(message);
|
||||
}
|
||||
|
||||
public Task SendToMultipleClients(string message, IReadOnlyList<string> connectionIds)
|
||||
public Task SendToMultipleClients(string message, List<string> connectionIds)
|
||||
{
|
||||
return Clients.Clients(connectionIds).Send(message);
|
||||
}
|
||||
|
|
@ -414,12 +414,12 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return Clients.Group(groupName).Send(message);
|
||||
}
|
||||
|
||||
public Task GroupExceptSendMethod(string groupName, string message, IReadOnlyList<string> excludedConnectionIds)
|
||||
public Task GroupExceptSendMethod(string groupName, string message, List<string> excludedConnectionIds)
|
||||
{
|
||||
return Clients.GroupExcept(groupName, excludedConnectionIds).Send(message);
|
||||
}
|
||||
|
||||
public Task SendToMultipleGroups(string message, IReadOnlyList<string> groupNames)
|
||||
public Task SendToMultipleGroups(string message, List<string> groupNames)
|
||||
{
|
||||
return Clients.Groups(groupNames).Send(message);
|
||||
}
|
||||
|
|
@ -434,7 +434,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
return Clients.All.Broadcast(message);
|
||||
}
|
||||
|
||||
public Task SendToAllExcept(string message, IReadOnlyList<string> excludedConnectionIds)
|
||||
public Task SendToAllExcept(string message, List<string> excludedConnectionIds)
|
||||
{
|
||||
return Clients.AllExcept(excludedConnectionIds).Send(message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2231,7 +2231,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Camel case is not the default yet")]
|
||||
public async Task JsonHubProtocolUsesCamelCasingByDefault()
|
||||
{
|
||||
using (StartVerifiableLog())
|
||||
|
|
@ -2934,7 +2934,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Object not supported yet")]
|
||||
public async Task UploadStreamedObjects()
|
||||
{
|
||||
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider();
|
||||
|
|
@ -2998,7 +2998,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Cyclic parsing is not supported yet")]
|
||||
public async Task ConnectionAbortedIfSendFailsWithProtocolError()
|
||||
{
|
||||
bool ExpectedErrors(WriteContext writeContext)
|
||||
|
|
@ -3028,7 +3028,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Magic auto cast not supported")]
|
||||
public async Task UploadStreamItemInvalidTypeAutoCasts()
|
||||
{
|
||||
using (StartVerifiableLog())
|
||||
|
|
@ -3050,6 +3050,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
await client.SendHubMessageAsync(CompletionMessage.Empty("id")).OrTimeout();
|
||||
var response = (CompletionMessage)await client.ReadAsync().OrTimeout();
|
||||
|
||||
Assert.Null(response.Error);
|
||||
Assert.Equal("510", response.Result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,14 +70,18 @@ namespace Microsoft.AspNetCore.SignalR.Common.Protocol.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void RegisteringMultipleHubProtocolsFails()
|
||||
public void RegisteringMultipleHubProtocolsReplacesWithLatest()
|
||||
{
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => new DefaultHubProtocolResolver(new[] {
|
||||
new NewtonsoftJsonHubProtocol(),
|
||||
new NewtonsoftJsonHubProtocol()
|
||||
}, NullLogger<DefaultHubProtocolResolver>.Instance));
|
||||
var jsonProtocol1 = new NewtonsoftJsonHubProtocol();
|
||||
var jsonProtocol2 = new NewtonsoftJsonHubProtocol();
|
||||
var resolver = new DefaultHubProtocolResolver(new[] {
|
||||
jsonProtocol1,
|
||||
jsonProtocol2
|
||||
}, NullLogger<DefaultHubProtocolResolver>.Instance);
|
||||
|
||||
Assert.Equal($"Multiple Hub Protocols with the name 'json' were registered.", exception.Message);
|
||||
var resolvedProtocol = resolver.GetProtocol(jsonProtocol2.Name, null);
|
||||
Assert.NotSame(jsonProtocol1, resolvedProtocol);
|
||||
Assert.Same(jsonProtocol2, resolvedProtocol);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> HubProtocolNames => HubProtocolHelpers.AllProtocols.Select(p => new object[] {p.Name});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Tests for users to verify their own implementations of SignalR types</Description>
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
<Reference Include="Microsoft.AspNetCore.SignalR.Common" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Core" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" />
|
||||
<Reference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" />
|
||||
<Reference Include="xunit.assert" />
|
||||
<Reference Include="xunit.extensibility.core" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
Loading…
Reference in New Issue