diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/MessagePackHubProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/MessagePackHubProtocol.cs index f972ad6a66..22e90fe437 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/MessagePackHubProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/MessagePackHubProtocol.cs @@ -20,10 +20,20 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol private const int VoidResult = 2; private const int NonVoidResult = 3; + private static readonly SerializationContext _serializationContext; + public string Name => "messagepack"; public ProtocolType Type => ProtocolType.Binary; + static MessagePackHubProtocol() + { + // 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; + } + public bool TryParseMessages(ReadOnlyBuffer input, IInvocationBinder binder, out IList messages) { messages = new List(); @@ -153,7 +163,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol packer.PackString(invocationMessage.InvocationId); packer.Pack(invocationMessage.NonBlocking); packer.PackString(invocationMessage.Target); - packer.PackObject(invocationMessage.Arguments); + packer.PackObject(invocationMessage.Arguments, _serializationContext); } private void WriteStreamingItemMessage(StreamItemMessage streamItemMessage, Packer packer, Stream output) @@ -161,7 +171,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol packer.PackArrayHeader(3); packer.Pack(StreamItemMessageType); packer.PackString(streamItemMessage.InvocationId); - packer.PackObject(streamItemMessage.Item); + packer.PackObject(streamItemMessage.Item, _serializationContext); } private void WriteCompletionMessage(CompletionMessage completionMessage, Packer packer, Stream output) @@ -181,7 +191,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol packer.PackString(completionMessage.Error); break; case NonVoidResult: - packer.PackObject(completionMessage.Result); + packer.PackObject(completionMessage.Result, _serializationContext); break; } } 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 eeccf7f75d..19ef3b4c4c 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs @@ -145,5 +145,82 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol Assert.True(result || messages.Count == 0); Assert.Equal(expectedMessagesCount, messages.Count); } + + public static IEnumerable MessageAndPayload => new object[][] + { + new object[] + { + new InvocationMessage("0", false, "A", 1, new CustomObject()), + new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x95, 0x01, 0xa1, 0x30, 0xc2, 0xa1, 0x41, + 0x92, // argument array + 0x01, // 1 - first argument + // 0x85 - a map of 5 items (properties) + 0x85, 0xac, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x50, 0x72, 0x6f, 0x70, 0xd3, + 0x08, 0xd4, 0x80, 0x6d, 0xb2, 0x76, 0xc0, 0x00, 0xaa, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x50, + 0x72, 0x6f, 0x70, 0xcb, 0x40, 0x19, 0x21, 0xfb, 0x54, 0x42, 0xcf, 0x12, 0xa7, 0x49, 0x6e, 0x74, + 0x50, 0x72, 0x6f, 0x70, 0x2a, 0xa8, 0x4e, 0x75, 0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x70, 0xc0, 0xaa, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x70, 0xa8, 0x53, 0x69, 0x67, 0x6e, 0x61, + 0x6c, 0x52, 0x21 + } + }, + new object[] + { + CompletionMessage.WithResult("0", new CustomObject()), + new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x94, 0x03, 0xa1, 0x30, 0x03, + // 0x85 - a map of 5 items (properties) + 0x85, 0xac, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x50, 0x72, 0x6f, 0x70, 0xd3, 0x08, + 0xd4, 0x80, 0x6d, 0xb2, 0x76, 0xc0, 0x00, 0xaa, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x50, 0x72, + 0x6f, 0x70, 0xcb, 0x40, 0x19, 0x21, 0xfb, 0x54, 0x42, 0xcf, 0x12, 0xa7, 0x49, 0x6e, 0x74, 0x50, + 0x72, 0x6f, 0x70, 0x2a, 0xa8, 0x4e, 0x75, 0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x70, 0xc0, 0xaa, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x70, 0xa8, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, + 0x52, 0x21 + } + }, + new object[] + { + new StreamItemMessage("0", new CustomObject()), + new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x93, 0x02, 0xa1, 0x30, + // 0x85 - a map of 5 items (properties) + 0x85, 0xac, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x50, 0x72, 0x6f, 0x70, 0xd3, 0x08, + 0xd4, 0x80, 0x6d, 0xb2, 0x76, 0xc0, 0x00, 0xaa, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x50, 0x72, + 0x6f, 0x70, 0xcb, 0x40, 0x19, 0x21, 0xfb, 0x54, 0x42, 0xcf, 0x12, 0xa7, 0x49, 0x6e, 0x74, 0x50, + 0x72, 0x6f, 0x70, 0x2a, 0xa8, 0x4e, 0x75, 0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x70, 0xc0, 0xaa, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x70, 0xa8, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, + 0x52, 0x21 + } + } + }; + + [Theory] + [MemberData(nameof(MessageAndPayload))] + public void UserObjectAreSerializedAsMaps(HubMessage message, byte[] expectedPayload) + { + using (var memoryStream = new MemoryStream()) + { + _hubProtocol.WriteMessage(message, memoryStream); + Assert.Equal(expectedPayload, memoryStream.ToArray()); + } + } + + [Fact] + public void CanWriteObjectsWithoutDefaultCtors() + { + var expectedPayload = new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x94, 0x03, 0xa1, 0x30, 0x03, 0x91, 0x2a + }; + + using (var memoryStream = new MemoryStream()) + { + _hubProtocol.WriteMessage(CompletionMessage.WithResult("0", new List { 42 }.AsReadOnly()), memoryStream); + Assert.Equal(expectedPayload, memoryStream.ToArray()); + } + } } }