diff --git a/src/SignalRServiceExtension/Config/DependencyInjectionExtensions.cs b/src/SignalRServiceExtension/Config/DependencyInjectionExtensions.cs index f5f024f..d24fe30 100644 --- a/src/SignalRServiceExtension/Config/DependencyInjectionExtensions.cs +++ b/src/SignalRServiceExtension/Config/DependencyInjectionExtensions.cs @@ -2,10 +2,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using Microsoft.AspNetCore.SignalR.Protocol; +using Microsoft.Azure.SignalR.Management; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; +using Newtonsoft.Json; namespace Microsoft.Azure.WebJobs.Extensions.SignalRService { @@ -15,18 +15,27 @@ internal static class DependencyInjectionExtensions public static IServiceCollection SetHubProtocol(this IServiceCollection services, IConfiguration configuration) { - if (Environment.Version.Major == 4 && configuration[Constants.AzureSignalRHubProtocol] != null) + var hubProtocolConfig = configuration[Constants.AzureSignalRHubProtocol]; + if (hubProtocolConfig is not null && Environment.Version.Major == 4) { - // Actually is .Net Core 2.x + // .Net Core 2.x is always Newtonsoft.Json. throw new InvalidOperationException(HubProtocolError); } -#if NETCOREAPP3_1 || NETCOREAPP3_0 || NETSTANDARD2_0 - else if (!DotnetRuntime(configuration) || UserSpecifyNewtonsoft(configuration)) + + //indicates Newtonsoft, camcelCase + if (configuration.GetValue(Constants.AzureSignalRNewtonsoftCamelCase, false)) { - // .Net Core 3.1, overwrite the System.Text.Json Protocol. - services.TryAddEnumerable(ServiceDescriptor.Singleton()); + // The default options is camelCase. + return services.AddNewtonsoftHubProtocol(o => { }); } -#endif + + if (!DotnetRuntime(configuration) || Enum.TryParse(hubProtocolConfig, out var result) && result == HubProtocol.NewtonsoftJson) + { + // Reset the options to keep backward compatibility. + return services.AddNewtonsoftHubProtocol(o => o.PayloadSerializerSettings = new JsonSerializerSettings()); + } + + //If hubProtocolConfig is SystemTextJson for .Net Core 3.1, do nothing, as transient mode doesn't accept it and persisent mode is already System.Text.Json by default. return services; } @@ -36,10 +45,5 @@ private static bool DotnetRuntime(IConfiguration configuration) //unit test environment return workerRuntime == null || workerRuntime == Constants.DotnetWorker; } - - private static bool UserSpecifyNewtonsoft(IConfiguration configuration) - { - return configuration.GetValue(Constants.AzureSignalRHubProtocol, HubProtocol.SystemTextJson) == HubProtocol.NewtonsoftJson; - } } } \ No newline at end of file diff --git a/src/SignalRServiceExtension/Constants.cs b/src/SignalRServiceExtension/Constants.cs index 79820d6..7e80fb0 100644 --- a/src/SignalRServiceExtension/Constants.cs +++ b/src/SignalRServiceExtension/Constants.cs @@ -8,6 +8,7 @@ internal static class Constants public const string AzureSignalRConnectionStringName = "AzureSignalRConnectionString"; public const string AzureSignalREndpoints = "Azure:SignalR:Endpoints"; public const string AzureSignalRHubProtocol = "Azure:SignalR:HubProtocol"; + public static string AzureSignalRNewtonsoftCamelCase = $"{AzureSignalRHubProtocol}:{HubProtocol.NewtonsoftJson}:CamelCase"; public const string ServiceTransportTypeName = "AzureSignalRServiceTransportType"; public const string AsrsHeaderPrefix = "X-ASRS-"; public const string AsrsConnectionIdHeader = AsrsHeaderPrefix + "Connection-Id"; diff --git a/test/SignalRServiceExtension.Tests/Config/DependencyInjectionExtensionFacts.cs b/test/SignalRServiceExtension.Tests/Config/DependencyInjectionExtensionFacts.cs deleted file mode 100644 index 5632979..0000000 --- a/test/SignalRServiceExtension.Tests/Config/DependencyInjectionExtensionFacts.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Linq; -using Microsoft.AspNetCore.SignalR.Protocol; -using Microsoft.Azure.WebJobs.Extensions.SignalRService; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace SignalRServiceExtension.Tests -{ - public class DependencyInjectionExtensionFacts - { - [Fact] - public void EmptyHubProtocolSetting_DoNothing() - { - var configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); - var services = new ServiceCollection() - .SetHubProtocol(configuration); - Assert.Empty(services); - } - -#if NETCOREAPP2_1 - - [Fact] - public void SetHubProtocol_Throw() - { - var configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); - configuration[Constants.AzureSignalRHubProtocol] = HubProtocol.SystemTextJson.ToString(); - var services = new ServiceCollection(); - Assert.Throws(() => services.SetHubProtocol(configuration)); - } - -#endif - -#if NETCOREAPP3_1 - - [Fact] - public void SetSystemTextJson_DoNothing() - { - var configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); - configuration[Constants.AzureSignalRHubProtocol] = HubProtocol.SystemTextJson.ToString(); - var services = new ServiceCollection() - .SetHubProtocol(configuration); - Assert.Empty(services); - } - - [Fact] - public void SetNewtonsoft() - { - var configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); - configuration[Constants.AzureSignalRHubProtocol] = HubProtocol.NewtonsoftJson.ToString(); - var services = new ServiceCollection() - .SetHubProtocol(configuration); - var hubProtocolImpl = services.Single().ImplementationType; - Assert.Equal(typeof(NewtonsoftJsonHubProtocol), hubProtocolImpl); - } -#endif - } -} \ No newline at end of file diff --git a/test/SignalRServiceExtension.Tests/Config/SetNewtonsoftHubProtocolFacts.cs b/test/SignalRServiceExtension.Tests/Config/SetNewtonsoftHubProtocolFacts.cs new file mode 100644 index 0000000..2d61283 --- /dev/null +++ b/test/SignalRServiceExtension.Tests/Config/SetNewtonsoftHubProtocolFacts.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; +using Microsoft.AspNetCore.SignalR.Protocol; +using Microsoft.Azure.SignalR.Management; +using Microsoft.Azure.SignalR.Tests.Common; +using Microsoft.Azure.WebJobs.Extensions.SignalRService; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Newtonsoft.Json.Serialization; +using Xunit; + +namespace SignalRServiceExtension.Tests +{ + public class SetNewtonsoftHubProtocolFacts + { + [Fact] + public void EmptyHubProtocolSetting_DoNothing() + { + var configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); + var services = new ServiceCollection() + .SetHubProtocol(configuration); + Assert.Empty(services); + } + +#if NETCOREAPP2_1 + + [Fact] + public void SetHubProtocol_Throw() + { + var configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); + configuration[Constants.AzureSignalRHubProtocol] = HubProtocol.SystemTextJson.ToString(); + var services = new ServiceCollection(); + Assert.Throws(() => services.SetHubProtocol(configuration)); + } + +#endif + +#if NETCOREAPP3_1 + + [Fact] + public void SetSystemTextJson_DoNothing() + { + var configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); + configuration[Constants.AzureSignalRHubProtocol] = HubProtocol.SystemTextJson.ToString(); + var services = new ServiceCollection() + .SetHubProtocol(configuration); + Assert.Empty(services); + } + + [Fact] + public async Task SetNewtonsoftPersistent() + { + var serviceManagerStore = CreateServiceManagerStore(out var configuration); + configuration[Constants.ServiceTransportTypeName] = "Persistent"; + var hubContext = await serviceManagerStore.GetOrAddByConnectionStringKey(Constants.AzureSignalRConnectionStringName).GetAsync("hub") as ServiceHubContextImpl; + var hubProtocol = hubContext.ServiceProvider.GetRequiredService(); + Assert.IsType(hubProtocol); + var newtonsoftOptions = hubContext.ServiceProvider.GetRequiredService>(); + Assert.Null(newtonsoftOptions.Value.PayloadSerializerSettings.ContractResolver); + } + + [Fact] + public async Task SetNewtonsoftInTransientModeAsync() + { + var serviceManagerStore = CreateServiceManagerStore(out var configuration); + var hubContext = await serviceManagerStore.GetOrAddByConnectionStringKey(Constants.AzureSignalRConnectionStringName).GetAsync("hub") as ServiceHubContextImpl; + + var restHubProtocol = hubContext.ServiceProvider.GetRequiredService(); + Assert.IsType(restHubProtocol); + var newtonsoftOptions = hubContext.ServiceProvider.GetRequiredService>(); + Assert.Null(newtonsoftOptions.Value.PayloadSerializerSettings.ContractResolver); + } + + [Fact] + public async Task SetNewtonsoftCamelCaseInTransientModeAsync() + { + var serviceManagerStore = CreateServiceManagerStore(out var configuration); + configuration["Azure:SignalR:HubProtocol:NewtonsoftJson:CamelCase"] = "true"; + + var hubContext = await serviceManagerStore.GetOrAddByConnectionStringKey(Constants.AzureSignalRConnectionStringName).GetAsync("hub") as ServiceHubContextImpl; + var restHubProtocol = hubContext.ServiceProvider.GetRequiredService(); + Assert.IsType(restHubProtocol); + var newtonsoftOptions = hubContext.ServiceProvider.GetRequiredService>(); + Assert.IsType(newtonsoftOptions.Value.PayloadSerializerSettings.ContractResolver); + } + + [Fact] + public async Task SetNewtonsoftCamelCaseInPersistentModeAsync() + { + var serviceManagerStore = CreateServiceManagerStore(out var configuration); + configuration["Azure:SignalR:HubProtocol:NewtonsoftJson:CamelCase"] = "true"; + configuration[Constants.ServiceTransportTypeName] = "Persistent"; + + var hubContext = await serviceManagerStore.GetOrAddByConnectionStringKey(Constants.AzureSignalRConnectionStringName).GetAsync("hub") as ServiceHubContextImpl; + var hubProtocol = hubContext.ServiceProvider.GetRequiredService(); + Assert.IsType(hubProtocol); + var newtonsoftOptions = hubContext.ServiceProvider.GetRequiredService>(); + Assert.IsType(newtonsoftOptions.Value.PayloadSerializerSettings.ContractResolver); + } + + private ServiceManagerStore CreateServiceManagerStore(out IConfiguration configuration) + { + configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); + configuration[Constants.AzureSignalRHubProtocol] = HubProtocol.NewtonsoftJson.ToString(); + configuration[Constants.AzureSignalRConnectionStringName] = FakeEndpointUtils.GetFakeConnectionString(1).Single(); + + return new ServiceManagerStore(configuration, NullLoggerFactory.Instance, null); + } +#endif + } +} \ No newline at end of file