diff --git a/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp3.0.cs b/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp3.0.cs index 17c98463a6..762408f9b1 100644 --- a/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp3.0.cs +++ b/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp3.0.cs @@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Builder public static partial class ComponentEndpointRouteBuilderExtensions { public static Microsoft.AspNetCore.Components.Server.ComponentEndpointConventionBuilder MapBlazorHub(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints) { throw null; } + public static Microsoft.AspNetCore.Components.Server.ComponentEndpointConventionBuilder MapBlazorHub(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, System.Type type, string selector) { throw null; } public static Microsoft.AspNetCore.Components.Server.ComponentEndpointConventionBuilder MapBlazorHub(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, System.Type componentType, string selector, string path) { throw null; } public static Microsoft.AspNetCore.Components.Server.ComponentEndpointConventionBuilder MapBlazorHub(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string selector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; } public static Microsoft.AspNetCore.Components.Server.ComponentEndpointConventionBuilder MapBlazorHub(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string selector, string path) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; } @@ -35,17 +36,6 @@ namespace Microsoft.AspNetCore.Components.Server internal ComponentEndpointConventionBuilder() { } public void Add(System.Action convention) { } } - public sealed partial class ComponentHub : Microsoft.AspNetCore.SignalR.Hub - { - public ComponentHub(System.IServiceProvider services, Microsoft.Extensions.Logging.ILogger logger) { } - public static Microsoft.AspNetCore.Http.PathString DefaultPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public void BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task ConnectCircuit(string circuitId) { throw null; } - public override System.Threading.Tasks.Task OnDisconnectedAsync(System.Exception exception) { throw null; } - public void OnRenderCompleted(long renderId, string errorMessageOrNull) { } - public string StartCircuit(string uriAbsolute, string baseUriAbsolute) { throw null; } - } public partial class ComponentPrerenderingContext { public ComponentPrerenderingContext() { } @@ -100,6 +90,14 @@ namespace Microsoft.Extensions.DependencyInjection { public static partial class ComponentServiceCollectionExtensions { - public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddServerSideBlazor(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServerSideBlazorBuilder AddServerSideBlazor(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + } + public partial interface IServerSideBlazorBuilder + { + Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; } + } + public static partial class ServerSizeBlazorBuilderExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServerSideBlazorBuilder AddHubOptions(this Microsoft.Extensions.DependencyInjection.IServerSideBlazorBuilder builder, System.Action configure) { throw null; } } } diff --git a/src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs b/src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs index f2674d8bcf..5a65519916 100644 --- a/src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs +++ b/src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs @@ -5,7 +5,6 @@ using System; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Server; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.SignalR; namespace Microsoft.AspNetCore.Builder { @@ -54,6 +53,37 @@ namespace Microsoft.AspNetCore.Builder return endpoints.MapBlazorHub(typeof(TComponent), selector, ComponentHub.DefaultPath); } + /// + /// Maps the SignalR to the path and associates + /// the component to this hub instance as the given DOM . + /// + /// The . + /// The first associated with this . + /// The selector for the component. + /// The . + public static ComponentEndpointConventionBuilder MapBlazorHub( + this IEndpointRouteBuilder endpoints, + Type type, + string selector) + { + if (endpoints == null) + { + throw new ArgumentNullException(nameof(endpoints)); + } + + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return endpoints.MapBlazorHub(type, selector, ComponentHub.DefaultPath); + } + /// /// Maps the SignalR to the path and associates /// the component to this hub instance as the given DOM . diff --git a/src/Components/Server/src/ComponentHub.cs b/src/Components/Server/src/ComponentHub.cs index 84b0267e6b..4dcb8873af 100644 --- a/src/Components/Server/src/ComponentHub.cs +++ b/src/Components/Server/src/ComponentHub.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Components.Server /// /// A SignalR hub that accepts connections to an ASP.NET Core Components application. /// - public sealed class ComponentHub : Hub + internal sealed class ComponentHub : Hub { private static readonly object CircuitKey = new object(); private readonly CircuitFactory _circuitFactory; diff --git a/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs b/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs index 043b9819e6..cc45d4e3b9 100644 --- a/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs +++ b/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs @@ -19,14 +19,24 @@ namespace Microsoft.Extensions.DependencyInjection public static class ComponentServiceCollectionExtensions { /// - /// Adds Razor Component app services to the service collection. + /// Adds Server-Side Blazor services to the service collection. /// /// The . - /// The . - public static IServiceCollection AddServerSideBlazor(this IServiceCollection services) + /// An that can be used to further customize the configuration. + public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollection services) { - services.AddSignalR() - .AddHubOptions(options => + var builder = new DefaultServerSideBlazorBuilder(services); + + // This call INTENTIONALLY uses the AddHubOptions on the SignalR builder, because it will merge + // the global HubOptions before running the configure callback. We want to ensure that happens + // once. Our AddHubOptions method doesn't do this. + // + // We need to restrict the set of protocols used by default to our specialized one. Users have + // the chance to modify options further via the builder. + // + // Other than the options, the things exposed by the SignalR builder aren't very meaningful in + // the Server-Side Blazor context and thus aren't exposed. + services.AddSignalR().AddHubOptions(options => { options.SupportedProtocols.Clear(); options.SupportedProtocols.Add(BlazorPackHubProtocol.ProtocolName); @@ -52,11 +62,23 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); // Standard razor component services implementations + // + // These intentionally replace the non-interactive versions included in MVC. services.AddScoped(); services.AddScoped(); services.AddScoped(); - return services; + return builder; + } + + private class DefaultServerSideBlazorBuilder : IServerSideBlazorBuilder + { + public DefaultServerSideBlazorBuilder(IServiceCollection services) + { + Services = services; + } + + public IServiceCollection Services { get; } } } } diff --git a/src/Components/Server/src/DependencyInjection/IServerSideBlazorBuilder.cs b/src/Components/Server/src/DependencyInjection/IServerSideBlazorBuilder.cs new file mode 100644 index 0000000000..d46eebc066 --- /dev/null +++ b/src/Components/Server/src/DependencyInjection/IServerSideBlazorBuilder.cs @@ -0,0 +1,16 @@ +// 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.Extensions.DependencyInjection +{ + /// + /// A builder that can be used to configure Server-Side Blazor. + /// + public interface IServerSideBlazorBuilder + { + /// + /// Gets the . + /// + IServiceCollection Services { get; } + } +} diff --git a/src/Components/Server/src/DependencyInjection/ServerSizeBlazorBuilderExtensions.cs b/src/Components/Server/src/DependencyInjection/ServerSizeBlazorBuilderExtensions.cs new file mode 100644 index 0000000000..4ed3a8aff3 --- /dev/null +++ b/src/Components/Server/src/DependencyInjection/ServerSizeBlazorBuilderExtensions.cs @@ -0,0 +1,36 @@ +// 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 Microsoft.AspNetCore.Components.Server; +using Microsoft.AspNetCore.SignalR; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Provides options for configuring Server-Side Blazor. + /// + public static class ServerSizeBlazorBuilderExtensions + { + /// + /// Adds hub options for the configuration of the SignalR Hub used by Server-Side Blazor. + /// + /// The . + /// A callback to configure the hub options. + /// The . + public static IServerSideBlazorBuilder AddHubOptions(this IServerSideBlazorBuilder builder, Action configure) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (configure != null) + { + builder.Services.Configure>(configure); + } + + return builder; + } + } +} diff --git a/src/Components/Server/test/DependencyInjection/ComponentServiceCollectionExtensionsTest.cs b/src/Components/Server/test/DependencyInjection/ComponentServiceCollectionExtensionsTest.cs new file mode 100644 index 0000000000..95f7db582b --- /dev/null +++ b/src/Components/Server/test/DependencyInjection/ComponentServiceCollectionExtensionsTest.cs @@ -0,0 +1,82 @@ +// 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 Microsoft.AspNetCore.Components.Server; +using Microsoft.AspNetCore.Components.Server.BlazorPack; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Extensions.DependencyInjection +{ + public class ComponentServiceCollectionExtensionsTest + { + [Fact] + public void AddServerSideSignalR_RegistersBlazorPack() + { + // Arrange + var services = new ServiceCollection(); + services.AddServerSideBlazor(); + + // Act + var options = services.BuildServiceProvider().GetRequiredService>>(); + + // Assert + var protocol = Assert.Single(options.Value.SupportedProtocols); + Assert.Equal(BlazorPackHubProtocol.ProtocolName, protocol); + } + + [Fact] + public void AddServerSideSignalR_RespectsGlobalHubOptions() + { + // Arrange + var services = new ServiceCollection(); + services.AddServerSideBlazor(); + + services.Configure(options => + { + options.SupportedProtocols.Add("test"); + options.HandshakeTimeout = TimeSpan.FromMinutes(10); + }); + + // Act + var options = services.BuildServiceProvider().GetRequiredService>>(); + + // Assert + var protocol = Assert.Single(options.Value.SupportedProtocols); + Assert.Equal(BlazorPackHubProtocol.ProtocolName, protocol); + Assert.Equal(TimeSpan.FromMinutes(10), options.Value.HandshakeTimeout); + } + + [Fact] + public void AddServerSideSignalR_ConfiguresGlobalOptionsBeforePerHubOptions() + { + // Arrange + var services = new ServiceCollection(); + services.AddServerSideBlazor().AddHubOptions(options => + { + Assert.Equal(TimeSpan.FromMinutes(10), options.HandshakeTimeout); + options.HandshakeTimeout = TimeSpan.FromMinutes(5); + }); + + services.Configure(options => + { + options.SupportedProtocols.Add("test"); + options.HandshakeTimeout = TimeSpan.FromMinutes(10); + }); + + // Act + var options = services.BuildServiceProvider().GetRequiredService>>(); + var globalOptions = services.BuildServiceProvider().GetRequiredService>(); + + // Assert + var protocol = Assert.Single(options.Value.SupportedProtocols); + Assert.Equal(BlazorPackHubProtocol.ProtocolName, protocol); + Assert.Equal(TimeSpan.FromMinutes(5), options.Value.HandshakeTimeout); + + // Configuring Blazor options is kept separate from the global options. + Assert.Equal(TimeSpan.FromMinutes(10), globalOptions.Value.HandshakeTimeout); + } + } +} diff --git a/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj b/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj index e49de9aa23..e25bf9b83d 100644 --- a/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj +++ b/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj @@ -15,10 +15,10 @@ - - - - + + + + diff --git a/src/Components/test/testassets/TestServer/Startup.cs b/src/Components/test/testassets/TestServer/Startup.cs index 18acb716a6..c2d551aa7b 100644 --- a/src/Components/test/testassets/TestServer/Startup.cs +++ b/src/Components/test/testassets/TestServer/Startup.cs @@ -55,18 +55,11 @@ namespace TestServer { subdirApp.UseClientSideBlazorFiles(); - // The following two lines are equivalent to: - // endpoints.MapComponentsHub(); - // - // However it's expressed using routing as a way of checking that - // we're not relying on any extra magic inside MapComponentsHub, since it's - // important that people can set up these bits of middleware manually (e.g., to - // swap in UseAzureSignalR instead of UseSignalR). subdirApp.UseRouting(); subdirApp.UseEndpoints(endpoints => { - endpoints.MapHub(ComponentHub.DefaultPath).AddComponent(typeof(Index), selector: "root"); + endpoints.MapBlazorHub(typeof(Index), selector: "root"); endpoints.MapFallbackToClientSideBlazor("index.html"); }); });