diff --git a/src/SignalR/perf/Microbenchmarks/DefaultHubDispatcherBenchmark.cs b/src/SignalR/perf/Microbenchmarks/DefaultHubDispatcherBenchmark.cs index 982ead5340..744c8cc311 100644 --- a/src/SignalR/perf/Microbenchmarks/DefaultHubDispatcherBenchmark.cs +++ b/src/SignalR/perf/Microbenchmarks/DefaultHubDispatcherBenchmark.cs @@ -37,8 +37,7 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks _dispatcher = new DefaultHubDispatcher( serviceScopeFactory, new HubContext(new DefaultHubLifetimeManager(NullLogger>.Instance)), - Options.Create(new HubOptions()), - Options.Create(new HubOptions()), + enableDetailedErrors: false, new Logger>(NullLoggerFactory.Instance)); var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default); diff --git a/src/SignalR/server/Core/src/HubConnectionHandler.cs b/src/SignalR/server/Core/src/HubConnectionHandler.cs index bd8b434168..663864cbb9 100644 --- a/src/SignalR/server/Core/src/HubConnectionHandler.cs +++ b/src/SignalR/server/Core/src/HubConnectionHandler.cs @@ -59,14 +59,22 @@ namespace Microsoft.AspNetCore.SignalR _logger = loggerFactory.CreateLogger>(); _userIdProvider = userIdProvider; - _enableDetailedErrors = _hubOptions.EnableDetailedErrors ?? _globalHubOptions.EnableDetailedErrors ?? false; - _maximumMessageSize = _hubOptions.MaximumReceiveMessageSize ?? _globalHubOptions.MaximumReceiveMessageSize; + _enableDetailedErrors = false; + if (_hubOptions.UserHasSetValues) + { + _maximumMessageSize = _hubOptions.MaximumReceiveMessageSize; + _enableDetailedErrors = _hubOptions.EnableDetailedErrors ?? _enableDetailedErrors; + } + else + { + _maximumMessageSize = _globalHubOptions.MaximumReceiveMessageSize; + _enableDetailedErrors = _globalHubOptions.EnableDetailedErrors ?? _enableDetailedErrors; + } _dispatcher = new DefaultHubDispatcher( serviceScopeFactory, new HubContext(lifetimeManager), - hubOptions, - globalHubOptions, + _enableDetailedErrors, new Logger>(loggerFactory)); } diff --git a/src/SignalR/server/Core/src/HubOptionsSetup`T.cs b/src/SignalR/server/Core/src/HubOptionsSetup`T.cs index 6296ac3c57..ee1ccdf1da 100644 --- a/src/SignalR/server/Core/src/HubOptionsSetup`T.cs +++ b/src/SignalR/server/Core/src/HubOptionsSetup`T.cs @@ -23,6 +23,12 @@ namespace Microsoft.AspNetCore.SignalR } options.KeepAliveInterval = _hubOptions.KeepAliveInterval; options.HandshakeTimeout = _hubOptions.HandshakeTimeout; + options.ClientTimeoutInterval = _hubOptions.ClientTimeoutInterval; + options.EnableDetailedErrors = _hubOptions.EnableDetailedErrors; + options.MaximumReceiveMessageSize = _hubOptions.MaximumReceiveMessageSize; + options.StreamBufferCapacity = _hubOptions.StreamBufferCapacity; + + options.UserHasSetValues = true; } } } diff --git a/src/SignalR/server/Core/src/HubOptions`T.cs b/src/SignalR/server/Core/src/HubOptions`T.cs index 1d69257bc3..88d7fc059b 100644 --- a/src/SignalR/server/Core/src/HubOptions`T.cs +++ b/src/SignalR/server/Core/src/HubOptions`T.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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 @@ -9,5 +9,6 @@ namespace Microsoft.AspNetCore.SignalR /// The hub type to configure. public class HubOptions : HubOptions where THub : Hub { + internal bool UserHasSetValues { get; set; } } } diff --git a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs index 0b7bd620ec..1f5faf714d 100644 --- a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs +++ b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs @@ -28,12 +28,11 @@ namespace Microsoft.AspNetCore.SignalR.Internal private readonly ILogger> _logger; private readonly bool _enableDetailedErrors; - public DefaultHubDispatcher(IServiceScopeFactory serviceScopeFactory, IHubContext hubContext, IOptions> hubOptions, - IOptions globalHubOptions, ILogger> logger) + public DefaultHubDispatcher(IServiceScopeFactory serviceScopeFactory, IHubContext hubContext, bool enableDetailedErrors, ILogger> logger) { _serviceScopeFactory = serviceScopeFactory; _hubContext = hubContext; - _enableDetailedErrors = hubOptions.Value.EnableDetailedErrors ?? globalHubOptions.Value.EnableDetailedErrors ?? false; + _enableDetailedErrors = enableDetailedErrors; _logger = logger; DiscoverHubMethods(); } diff --git a/src/SignalR/server/SignalR/src/SignalRDependencyInjectionExtensions.cs b/src/SignalR/server/SignalR/src/SignalRDependencyInjectionExtensions.cs index a7ec121154..918658fc90 100644 --- a/src/SignalR/server/SignalR/src/SignalRDependencyInjectionExtensions.cs +++ b/src/SignalR/server/SignalR/src/SignalRDependencyInjectionExtensions.cs @@ -4,7 +4,6 @@ using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.SignalR; -using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -67,8 +66,10 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(services)); } - return services.Configure(configure) - .AddSignalR(); + var signalrBuilder = services.AddSignalR(); + // Setup users settings after we've setup ours + services.Configure(configure); + return signalrBuilder; } } } diff --git a/src/SignalR/server/SignalR/test/AddSignalRTests.cs b/src/SignalR/server/SignalR/test/AddSignalRTests.cs index 871a16ffa3..d711f242b5 100644 --- a/src/SignalR/server/SignalR/test/AddSignalRTests.cs +++ b/src/SignalR/server/SignalR/test/AddSignalRTests.cs @@ -1,6 +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.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -81,6 +82,29 @@ namespace Microsoft.AspNetCore.SignalR.Tests Assert.Equal(0, serviceProvider.GetRequiredService>>().Value.SupportedProtocols.Count); } + [Fact] + public void HubSpecificOptionsHaveSameValuesAsGlobalHubOptions() + { + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddSignalR().AddHubOptions(options => + { + }); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + var hubOptions = serviceProvider.GetRequiredService>>().Value; + var globalHubOptions = serviceProvider.GetRequiredService>().Value; + + Assert.Equal(globalHubOptions.MaximumReceiveMessageSize, hubOptions.MaximumReceiveMessageSize); + Assert.Equal(globalHubOptions.StreamBufferCapacity, hubOptions.StreamBufferCapacity); + Assert.Equal(globalHubOptions.EnableDetailedErrors, hubOptions.EnableDetailedErrors); + Assert.Equal(globalHubOptions.KeepAliveInterval, hubOptions.KeepAliveInterval); + Assert.Equal(globalHubOptions.HandshakeTimeout, hubOptions.HandshakeTimeout); + Assert.Equal(globalHubOptions.SupportedProtocols, hubOptions.SupportedProtocols); + Assert.Equal(globalHubOptions.ClientTimeoutInterval, hubOptions.ClientTimeoutInterval); + Assert.True(hubOptions.UserHasSetValues); + } + [Fact] public void StreamBufferCapacityGetSet() { @@ -94,6 +118,36 @@ namespace Microsoft.AspNetCore.SignalR.Tests var serviceProvider = serviceCollection.BuildServiceProvider(); Assert.Equal(42, serviceProvider.GetRequiredService>>().Value.StreamBufferCapacity); } + + [Fact] + public void UserSpecifiedOptionsRunAfterDefaultOptions() + { + var serviceCollection = new ServiceCollection(); + + // null is special when the default options setup runs, so we set to null to verify that our options run after the default + // setup runs + serviceCollection.AddSignalR(options => + { + options.MaximumReceiveMessageSize = null; + options.StreamBufferCapacity = null; + options.EnableDetailedErrors = null; + options.KeepAliveInterval = null; + options.HandshakeTimeout = null; + options.SupportedProtocols = null; + options.ClientTimeoutInterval = TimeSpan.FromSeconds(1); + }); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var globalOptions = serviceProvider.GetRequiredService>().Value; + Assert.Null(globalOptions.MaximumReceiveMessageSize); + Assert.Null(globalOptions.StreamBufferCapacity); + Assert.Null(globalOptions.EnableDetailedErrors); + Assert.Null(globalOptions.KeepAliveInterval); + Assert.Null(globalOptions.HandshakeTimeout); + Assert.Null(globalOptions.SupportedProtocols); + Assert.Equal(TimeSpan.FromSeconds(1), globalOptions.ClientTimeoutInterval); + } } public class CustomHub : Hub diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs index c88f593db2..9f727d6523 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs @@ -3834,6 +3834,38 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } + [Fact] + public async Task SpecificHubOptionForMaximumReceiveMessageSizeIsUsedOverGlobalHubOption() + { + var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(serviceBuilder => + { + serviceBuilder.AddSignalR(o => + { + // ConnectAsync would fail if this value was used + o.MaximumReceiveMessageSize = 1; + }).AddHubOptions(o => + { + // null is treated as both no-limit and not set, this test verifies that we track if the user explicitly sets the value + o.MaximumReceiveMessageSize = null; + }); + }); + var connectionHandler = serviceProvider.GetService>(); + + using (StartVerifiableLog()) + { + using var client = new TestClient(); + + var connectionHandlerTask = await client.ConnectAsync(connectionHandler); + + // Wait for a connection, or for the endpoint to fail. + await client.Connected.OrThrowIfOtherFails(connectionHandlerTask).OrTimeout(); + + await client.DisposeAsync().OrTimeout(); + + await connectionHandlerTask.OrTimeout(); + } + } + private class CustomHubActivator : IHubActivator where THub : Hub { public int ReleaseCount;