From ba25dee1417b882a1c0993118a4fe6cfaa98c3fc Mon Sep 17 00:00:00 2001 From: Pawel Kadluczka Date: Tue, 19 Sep 2017 17:32:00 -0700 Subject: [PATCH] Enabling customizing serialization settings in MessagePack protocol --- .../HubConnectionBuilderExtensions.cs | 3 +- .../Protocol/MessagePackHubProtocol.cs | 21 ++++--- .../HubOptions.cs | 3 + .../Internal/DefaultHubProtocolResolver.cs | 2 +- .../HubConnectionTests.cs | 2 +- .../HubConnectionProtocolTests.cs | 9 ++- .../Protocol/MessagePackHubProtocolTests.cs | 3 +- .../HubEndpointTests.cs | 56 ++++++++++++++++++- .../DefaultHubProtocolResolverTests.cs | 2 +- ...gnalRDependencyInjectionExtensionsTests.cs | 15 +++++ .../TestClient.cs | 4 +- 11 files changed, 101 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnectionBuilderExtensions.cs b/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnectionBuilderExtensions.cs index a6b770d6cc..fdfe9aef73 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnectionBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnectionBuilderExtensions.cs @@ -23,7 +23,8 @@ namespace Microsoft.AspNetCore.SignalR.Client public static IHubConnectionBuilder WithMessagePackProtocol(this IHubConnectionBuilder hubConnectionBuilder) { - return hubConnectionBuilder.WithHubProtocol(new MessagePackHubProtocol()); + return hubConnectionBuilder.WithHubProtocol( + new MessagePackHubProtocol(MessagePackHubProtocol.CreateDefaultSerializationContext())); } public static IHubConnectionBuilder WithLoggerFactory(this IHubConnectionBuilder hubConnectionBuilder, ILoggerFactory loggerFactory) diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/MessagePackHubProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/MessagePackHubProtocol.cs index 22e90fe437..77ad478fc1 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/MessagePackHubProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/MessagePackHubProtocol.cs @@ -20,18 +20,15 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol private const int VoidResult = 2; private const int NonVoidResult = 3; - private static readonly SerializationContext _serializationContext; + private readonly SerializationContext _serializationContext; public string Name => "messagepack"; public ProtocolType Type => ProtocolType.Binary; - static MessagePackHubProtocol() + public MessagePackHubProtocol(SerializationContext serializationContext) { - // serializes objects (here: arguments and results) as maps so that property names are preserved - _serializationContext = new SerializationContext { SerializationMethod = SerializationMethod.Map }; - // allows for serializing objects that cannot be deserialized due to the lack of the default ctor etc. - _serializationContext.CompatibilityOptions.AllowAsymmetricSerializer = true; + _serializationContext = serializationContext; } public bool TryParseMessages(ReadOnlyBuffer input, IInvocationBinder binder, out IList messages) @@ -156,7 +153,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol } } - private static void WriteInvocationMessage(InvocationMessage invocationMessage, Packer packer, Stream output) + private void WriteInvocationMessage(InvocationMessage invocationMessage, Packer packer, Stream output) { packer.PackArrayHeader(5); packer.Pack(InvocationMessageType); @@ -291,5 +288,15 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol throw new FormatException($"Deserializing object of the `{type.Name}` type for '{field}' failed.", msgPackException); } + + public static SerializationContext CreateDefaultSerializationContext() + { + // serializes objects (here: arguments and results) as maps so that property names are preserved + var serializationContext = new SerializationContext { SerializationMethod = SerializationMethod.Map }; + // allows for serializing objects that cannot be deserialized due to the lack of the default ctor etc. + serializationContext.CompatibilityOptions.AllowAsymmetricSerializer = true; + + return serializationContext; + } } } diff --git a/src/Microsoft.AspNetCore.SignalR.Core/HubOptions.cs b/src/Microsoft.AspNetCore.SignalR.Core/HubOptions.cs index b1d6f40cdd..b1eebb66fd 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/HubOptions.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/HubOptions.cs @@ -1,6 +1,8 @@ // 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.Internal.Protocol; +using MsgPack.Serialization; using Newtonsoft.Json; namespace Microsoft.AspNetCore.SignalR @@ -8,5 +10,6 @@ namespace Microsoft.AspNetCore.SignalR public class HubOptions { public JsonSerializerSettings JsonSerializerSettings { get; set; } = new JsonSerializerSettings(); + public SerializationContext MessagePackSerializationContext { get; set; } = MessagePackHubProtocol.CreateDefaultSerializationContext(); } } diff --git a/src/Microsoft.AspNetCore.SignalR.Core/Internal/DefaultHubProtocolResolver.cs b/src/Microsoft.AspNetCore.SignalR.Core/Internal/DefaultHubProtocolResolver.cs index bb0d44bf97..fe2a59f7bd 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/Internal/DefaultHubProtocolResolver.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/Internal/DefaultHubProtocolResolver.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal case "json": return new JsonHubProtocol(JsonSerializer.Create(_options.Value.JsonSerializerSettings)); case "messagepack": - return new MessagePackHubProtocol(); + return new MessagePackHubProtocol(_options.Value.MessagePackSerializationContext); default: throw new NotSupportedException($"The protocol '{protocolName ?? "(null)"}' is not supported."); } diff --git a/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs index e92ee22a3f..39f7ab26ac 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs @@ -246,7 +246,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests new IHubProtocol[] { new JsonHubProtocol(new JsonSerializer()), - new MessagePackHubProtocol(), + new MessagePackHubProtocol(MessagePackHubProtocol.CreateDefaultSerializationContext()), }; public static IEnumerable TransportTypes() diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionProtocolTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionProtocolTests.cs index c9a8928b4a..d88054e2b9 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionProtocolTests.cs @@ -331,7 +331,8 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests { var connection = new TestConnection(TransferMode.Text); - var hubConnection = new HubConnection(connection, new MessagePackHubProtocol(), new LoggerFactory()); + var hubConnection = new HubConnection(connection, + new MessagePackHubProtocol(MessagePackHubProtocol.CreateDefaultSerializationContext()), new LoggerFactory()); try { await hubConnection.StartAsync().OrTimeout(); @@ -361,7 +362,8 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests public async Task MessagesDecodedWhenUsingBinaryProtocolOverTextTransport() { var connection = new TestConnection(TransferMode.Text); - var hubConnection = new HubConnection(connection, new MessagePackHubProtocol(), new LoggerFactory()); + var hubConnection = new HubConnection(connection, + new MessagePackHubProtocol(MessagePackHubProtocol.CreateDefaultSerializationContext()), new LoggerFactory()); var invocationTcs = new TaskCompletionSource(); try @@ -371,7 +373,8 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests using (var ms = new MemoryStream()) { - new MessagePackHubProtocol().WriteMessage(new InvocationMessage("1", true, "MyMethod", 42), ms); + new MessagePackHubProtocol(MessagePackHubProtocol.CreateDefaultSerializationContext()) + .WriteMessage(new InvocationMessage("1", true, "MyMethod", 42), ms); var invokeMessage = Convert.ToBase64String(ms.ToArray()); var payloadSize = invokeMessage.Length.ToString(CultureInfo.InvariantCulture); diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs index 19ef3b4c4c..32c45ae11d 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs @@ -12,7 +12,8 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol { public class MessagePackHubProtocolTests { - private static readonly MessagePackHubProtocol _hubProtocol = new MessagePackHubProtocol(); + private static readonly MessagePackHubProtocol _hubProtocol + = new MessagePackHubProtocol(MessagePackHubProtocol.CreateDefaultSerializationContext()); public static IEnumerable TestMessages => new[] { diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/HubEndpointTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/HubEndpointTests.cs index 9b44396d1b..104c32739c 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/HubEndpointTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/HubEndpointTests.cs @@ -13,8 +13,11 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.SignalR.Tests.Common; using Microsoft.AspNetCore.Sockets; +using Microsoft.AspNetCore.Sockets.Features; using Microsoft.Extensions.DependencyInjection; using Moq; +using MsgPack; +using MsgPack.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Xunit; @@ -1020,7 +1023,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests await client.SendInvocationAsync(nameof(MethodHub.BroadcastItem)).OrTimeout(); - var message = await client.ReadAsync().OrTimeout() as InvocationMessage; + var message = (InvocationMessage)await client.ReadAsync().OrTimeout(); var customItem = message.Arguments[0].ToString(); // Originally "Message" and "paramName" @@ -1033,6 +1036,46 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } + [Fact] + public async Task HubOptionsCanUseCustomMessagePackSettings() + { + var serializationContext = MessagePackHubProtocol.CreateDefaultSerializationContext(); + serializationContext.SerializationMethod = SerializationMethod.Array; + + var serviceProvider = CreateServiceProvider(services => + { + services.AddSignalR(options => + { + options.MessagePackSerializationContext = serializationContext; + }); + }); + + var endPoint = serviceProvider.GetService>(); + + using (var client = new TestClient(synchronousCallbacks: false, protocol: new MessagePackHubProtocol(serializationContext))) + { + var transportFeature = new Mock(); + transportFeature.SetupGet(f => f.TransportCapabilities).Returns(TransferMode.Binary); + client.Connection.Features.Set(transportFeature.Object); + var endPointLifetime = endPoint.OnConnectedAsync(client.Connection); + + await client.Connected.OrTimeout(); + + await client.SendInvocationAsync(nameof(MethodHub.BroadcastItem)).OrTimeout(); + + var message = await client.ReadAsync().OrTimeout() as InvocationMessage; + + var msgPackObject = Assert.IsType(message.Arguments[0]); + // Custom serialization - object was serialized as an array and not a map + Assert.True(msgPackObject.IsArray); + Assert.Equal(new[] { "test", "param" }, ((MessagePackObject[])msgPackObject.ToObject()).Select(o => o.AsString())); + + client.Dispose(); + + await endPointLifetime.OrTimeout(); + } + } + [Fact] public async Task CanGetHttpContextFromHubConnectionContext() { @@ -1427,6 +1470,15 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } + public class Result + { + public string Message { get; set; } +#pragma warning disable IDE1006 // Naming Styles + // testing casing + public string paramName { get; set; } +#pragma warning restore IDE1006 // Naming Styles + } + private class MethodHub : TestHub { public Task GroupRemoveMethod(string groupName) @@ -1461,7 +1513,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests public Task BroadcastItem() { - return Clients.All.InvokeAsync("Broadcast", new { Message = "test", paramName = "test" }); + return Clients.All.InvokeAsync("Broadcast", new Result { Message = "test", paramName = "param" }); } public Task TaskValueMethod() diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Internal/DefaultHubProtocolResolverTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Internal/DefaultHubProtocolResolverTests.cs index 39c77de93e..0b3b604182 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Internal/DefaultHubProtocolResolverTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Internal/DefaultHubProtocolResolverTests.cs @@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Protocol.Tests new[] { new object[] { new JsonHubProtocol(new JsonSerializer()) }, - new object[] { new MessagePackHubProtocol() }, + new object[] { new MessagePackHubProtocol(MessagePackHubProtocol.CreateDefaultSerializationContext()) }, }; } } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/SignalRDependencyInjectionExtensionsTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/SignalRDependencyInjectionExtensionsTests.cs index a4f834f97d..3652afb2de 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/SignalRDependencyInjectionExtensionsTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/SignalRDependencyInjectionExtensionsTests.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using MsgPack.Serialization; using Xunit; namespace Microsoft.AspNetCore.SignalR.Tests @@ -19,5 +20,19 @@ namespace Microsoft.AspNetCore.SignalR.Tests var hubOptions = serviceProvider.GetService>(); Assert.NotNull(hubOptions.Value.JsonSerializerSettings); } + + [Fact] + public void MessagePackSerializationContextInOptionsIsSetAndHasDefaultSettings() + { + var services = new ServiceCollection(); + services.AddOptions(); + services.AddSignalR(); + var serviceProvider = services.BuildServiceProvider(); + var hubOptions = serviceProvider.GetService>(); + var serializationContext = hubOptions.Value.MessagePackSerializationContext; + Assert.NotNull(serializationContext); + Assert.Equal(SerializationMethod.Map, serializationContext.SerializationMethod); + Assert.True(serializationContext.CompatibilityOptions.AllowAsymmetricSerializer); + } } } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/TestClient.cs b/test/Microsoft.AspNetCore.SignalR.Tests/TestClient.cs index e589fefff1..94725fdebe 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/TestClient.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/TestClient.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests public Channel Application { get; } public Task Connected => ((TaskCompletionSource)Connection.Metadata["ConnectedTask"]).Task; - public TestClient(bool synchronousCallbacks = false) + public TestClient(bool synchronousCallbacks = false, IHubProtocol protocol = null) { var options = new ChannelOptimizations { AllowSynchronousContinuations = synchronousCallbacks }; var transportToApplication = Channel.CreateUnbounded(options); @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests Connection.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, Interlocked.Increment(ref _id).ToString()) })); Connection.Metadata["ConnectedTask"] = new TaskCompletionSource(); - var protocol = new JsonHubProtocol(new JsonSerializer()); + protocol = protocol ?? new JsonHubProtocol(new JsonSerializer()); _protocolReaderWriter = new HubProtocolReaderWriter(protocol, new PassThroughEncoder()); _cts = new CancellationTokenSource();