From a6fd8da9e87d29515db3196cae7b70ea40f90381 Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Fri, 19 Apr 2019 09:55:51 -0700 Subject: [PATCH] Set camelCase as default in JsonHubProtocol (#9531) --- .../clients/ts/FunctionalTests/Startup.cs | 6 ++ ...re.SignalR.Protocols.Json.netcoreapp3.0.cs | 1 + ...e.SignalR.Protocols.Json.netstandard2.0.cs | 1 + .../src/JsonHubProtocolOptions.cs | 15 +++++ .../src/Protocol/JsonHubProtocol.cs | 2 +- .../Internal/Protocol/JsonHubProtocolTests.cs | 59 ++++++++++++++++++- 6 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/SignalR/clients/ts/FunctionalTests/Startup.cs b/src/SignalR/clients/ts/FunctionalTests/Startup.cs index 8926142fb5..30d0b1e770 100644 --- a/src/SignalR/clients/ts/FunctionalTests/Startup.cs +++ b/src/SignalR/clients/ts/FunctionalTests/Startup.cs @@ -41,6 +41,12 @@ namespace FunctionalTests { options.EnableDetailedErrors = true; }) + .AddJsonProtocol(options => + { + // we are running the same tests with JSON and MsgPack protocols and having + // consistent casing makes it cleaner to verify results + options.UseCamelCase = false; + }) .AddMessagePackProtocol(); services.AddCors(); diff --git a/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netcoreapp3.0.cs b/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netcoreapp3.0.cs index 802ff18c1e..aa468508cd 100644 --- a/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netcoreapp3.0.cs +++ b/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netcoreapp3.0.cs @@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.SignalR public JsonHubProtocolOptions() { } public bool AllowTrailingCommas { get { throw null; } set { } } public bool IgnoreNullValues { get { throw null; } set { } } + public bool UseCamelCase { get { throw null; } set { } } public bool WriteIndented { get { throw null; } set { } } } } diff --git a/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netstandard2.0.cs b/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netstandard2.0.cs index 802ff18c1e..aa468508cd 100644 --- a/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netstandard2.0.cs +++ b/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netstandard2.0.cs @@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.SignalR public JsonHubProtocolOptions() { } public bool AllowTrailingCommas { get { throw null; } set { } } public bool IgnoreNullValues { get { throw null; } set { } } + public bool UseCamelCase { get { throw null; } set { } } public bool WriteIndented { get { throw null; } set { } } } } diff --git a/src/SignalR/common/Protocols.Json/src/JsonHubProtocolOptions.cs b/src/SignalR/common/Protocols.Json/src/JsonHubProtocolOptions.cs index b959541a3f..ca42ae3312 100644 --- a/src/SignalR/common/Protocols.Json/src/JsonHubProtocolOptions.cs +++ b/src/SignalR/common/Protocols.Json/src/JsonHubProtocolOptions.cs @@ -21,5 +21,20 @@ namespace Microsoft.AspNetCore.SignalR public bool IgnoreNullValues { get => _serializerOptions.IgnoreNullValues; set => _serializerOptions.IgnoreNullValues = value; } public bool WriteIndented { get => _serializerOptions.WriteIndented; set => _serializerOptions.WriteIndented = value; } public bool AllowTrailingCommas { get => _serializerOptions.AllowTrailingCommas; set => _serializerOptions.AllowTrailingCommas = value; } + public bool UseCamelCase + { + get => _serializerOptions.PropertyNamingPolicy == JsonNamingPolicy.CamelCase; + set + { + if (value) + { + _serializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + } + else + { + _serializerOptions.PropertyNamingPolicy = null; + } + } + } } } diff --git a/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs b/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs index 89d2989aeb..3f59d02c8a 100644 --- a/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs +++ b/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs @@ -770,7 +770,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol options.AllowTrailingCommas = false; options.IgnoreNullValues = false; options.IgnoreReadOnlyProperties = false; - // TODO: camelCase + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; return options; } diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs index aa70969206..f6de0069aa 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol var protocolOptions = new JsonHubProtocolOptions() { IgnoreNullValues = ignoreNullValues, - //TODO: camelCase + UseCamelCase = useCamelCase, }; return new JsonHubProtocol(Options.Create(protocolOptions)); @@ -102,8 +102,65 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol new JsonProtocolTestData("CompletionMessage_HasFloatResult", CompletionMessage.WithResult("123", 2.0f), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":2}"), new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"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" }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20\\u002b12:34\"]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":[1,2,3]}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":[1,2,3]}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":[1,2,3]}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":[1,2,3]}]}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":[1,2,3]}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":[1,2,3]}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":[1,2,3]}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":[1,2,3]}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":[1,2,3]}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":[1,2,3]}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":[1,2,3]}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":[1,2,3]}}"), + new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, true, "{\"type\":3,\"invocationId\":\"123\"}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":[1,2,3]}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":[1,2,3]}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, false, "{\"type\":4,\"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("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":[1,2,3]}]}"), }.ToDictionary(t => t.Name); public static IEnumerable CustomProtocolTestDataNames => CustomProtocolTestData.Keys.Select(name => new object[] { name }); } + + // Revert back to CustomObject when initial values on arrays are supported + // e.g. byte[] arr { get; set; } = byte[] { 1, 2, 3 }; + internal class TemporaryCustomObject : IEquatable + { + // Not intended to be a full set of things, just a smattering of sample serializations + public string StringProp { get; set; } = "SignalR!"; + + public double DoubleProp { get; set; } = 6.2831853071; + + public int IntProp { get; set; } = 42; + + public DateTime DateTimeProp { get; set; } = new DateTime(2017, 4, 11, 0, 0, 0, DateTimeKind.Utc); + + public object NullProp { get; set; } = null; + + public byte[] ByteArrProp { get; set; } + + public override bool Equals(object obj) + { + return obj is TemporaryCustomObject o && Equals(o); + } + + public override int GetHashCode() + { + // This is never used in a hash table + return 0; + } + + public bool Equals(TemporaryCustomObject right) + { + // This allows the comparer below to properly compare the object in the test. + return string.Equals(StringProp, right.StringProp, StringComparison.Ordinal) && + DoubleProp == right.DoubleProp && + IntProp == right.IntProp && + DateTime.Equals(DateTimeProp, right.DateTimeProp) && + NullProp == right.NullProp && + System.Linq.Enumerable.SequenceEqual(ByteArrProp, right.ByteArrProp); + } + } }