Skip to content

Commit b383695

Browse files
committed
Fix #9316 Expose Blazor HubOptions
1 parent 16c01d5 commit b383695

9 files changed

+209
-32
lines changed

src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp3.0.cs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public static partial class ComponentEndpointConventionBuilderExtensions
1010
public static partial class ComponentEndpointRouteBuilderExtensions
1111
{
1212
public static Microsoft.AspNetCore.Components.Server.ComponentEndpointConventionBuilder MapBlazorHub(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints) { throw null; }
13+
public static Microsoft.AspNetCore.Components.Server.ComponentEndpointConventionBuilder MapBlazorHub(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, System.Type type, string selector) { throw null; }
1314
public static Microsoft.AspNetCore.Components.Server.ComponentEndpointConventionBuilder MapBlazorHub(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, System.Type componentType, string selector, string path) { throw null; }
1415
public static Microsoft.AspNetCore.Components.Server.ComponentEndpointConventionBuilder MapBlazorHub<TComponent>(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string selector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
1516
public static Microsoft.AspNetCore.Components.Server.ComponentEndpointConventionBuilder MapBlazorHub<TComponent>(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string selector, string path) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
@@ -35,17 +36,6 @@ public sealed partial class ComponentEndpointConventionBuilder : Microsoft.AspNe
3536
internal ComponentEndpointConventionBuilder() { }
3637
public void Add(System.Action<Microsoft.AspNetCore.Builder.EndpointBuilder> convention) { }
3738
}
38-
public sealed partial class ComponentHub : Microsoft.AspNetCore.SignalR.Hub
39-
{
40-
public ComponentHub(System.IServiceProvider services, Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Components.Server.ComponentHub> logger) { }
41-
public static Microsoft.AspNetCore.Http.PathString DefaultPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
42-
public void BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { }
43-
[System.Diagnostics.DebuggerStepThroughAttribute]
44-
public System.Threading.Tasks.Task<bool> ConnectCircuit(string circuitId) { throw null; }
45-
public override System.Threading.Tasks.Task OnDisconnectedAsync(System.Exception exception) { throw null; }
46-
public void OnRenderCompleted(long renderId, string errorMessageOrNull) { }
47-
public string StartCircuit(string uriAbsolute, string baseUriAbsolute) { throw null; }
48-
}
4939
public partial class ComponentPrerenderingContext
5040
{
5141
public ComponentPrerenderingContext() { }
@@ -100,6 +90,14 @@ namespace Microsoft.Extensions.DependencyInjection
10090
{
10191
public static partial class ComponentServiceCollectionExtensions
10292
{
103-
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddServerSideBlazor(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
93+
public static Microsoft.Extensions.DependencyInjection.IServerSideBlazorBuilder AddServerSideBlazor(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
94+
}
95+
public partial interface IServerSideBlazorBuilder
96+
{
97+
Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; }
98+
}
99+
public static partial class ServerSizeBlazorBuilderExtensions
100+
{
101+
public static Microsoft.Extensions.DependencyInjection.IServerSideBlazorBuilder AddHubOptions(this Microsoft.Extensions.DependencyInjection.IServerSideBlazorBuilder builder, System.Action<Microsoft.AspNetCore.SignalR.HubOptions> configure) { throw null; }
104102
}
105103
}

src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using Microsoft.AspNetCore.Components;
66
using Microsoft.AspNetCore.Components.Server;
77
using Microsoft.AspNetCore.Routing;
8-
using Microsoft.AspNetCore.SignalR;
98

109
namespace Microsoft.AspNetCore.Builder
1110
{
@@ -54,6 +53,37 @@ public static ComponentEndpointConventionBuilder MapBlazorHub<TComponent>(
5453
return endpoints.MapBlazorHub(typeof(TComponent), selector, ComponentHub.DefaultPath);
5554
}
5655

56+
/// <summary>
57+
/// Maps the SignalR <see cref="ComponentHub"/> to the path <see cref="ComponentHub.DefaultPath"/> and associates
58+
/// the component <paramref name="type"/> to this hub instance as the given DOM <paramref name="selector"/>.
59+
/// </summary>
60+
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
61+
/// <param name="type">The first <see cref="IComponent"/> associated with this <see cref="ComponentHub"/>.</param>
62+
/// <param name="selector">The selector for the component.</param>
63+
/// <returns>The <see cref="ComponentEndpointConventionBuilder"/>.</returns>
64+
public static ComponentEndpointConventionBuilder MapBlazorHub(
65+
this IEndpointRouteBuilder endpoints,
66+
Type type,
67+
string selector)
68+
{
69+
if (endpoints == null)
70+
{
71+
throw new ArgumentNullException(nameof(endpoints));
72+
}
73+
74+
if (type == null)
75+
{
76+
throw new ArgumentNullException(nameof(type));
77+
}
78+
79+
if (selector == null)
80+
{
81+
throw new ArgumentNullException(nameof(selector));
82+
}
83+
84+
return endpoints.MapBlazorHub(type, selector, ComponentHub.DefaultPath);
85+
}
86+
5787
/// <summary>
5888
/// Maps the SignalR <see cref="ComponentHub"/> to the path <paramref name="path"/> and associates
5989
/// the component <typeparamref name="TComponent"/> to this hub instance as the given DOM <paramref name="selector"/>.

src/Components/Server/src/ComponentHub.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Components.Server
1515
/// <summary>
1616
/// A SignalR hub that accepts connections to an ASP.NET Core Components application.
1717
/// </summary>
18-
public sealed class ComponentHub : Hub
18+
internal sealed class ComponentHub : Hub
1919
{
2020
private static readonly object CircuitKey = new object();
2121
private readonly CircuitFactory _circuitFactory;

src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,24 @@ namespace Microsoft.Extensions.DependencyInjection
1919
public static class ComponentServiceCollectionExtensions
2020
{
2121
/// <summary>
22-
/// Adds Razor Component app services to the service collection.
22+
/// Adds Server-Side Blazor services to the service collection.
2323
/// </summary>
2424
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
25-
/// <returns>The <see cref="IServiceCollection"/>.</returns>
26-
public static IServiceCollection AddServerSideBlazor(this IServiceCollection services)
25+
/// <returns>An <see cref="IServerSideBlazorBuilder"/> that can be used to further customize the configuration.</returns>
26+
public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollection services)
2727
{
28-
services.AddSignalR()
29-
.AddHubOptions<ComponentHub>(options =>
28+
var builder = new DefaultServerSideBlazorBuilder(services);
29+
30+
// This call INTENTIONALLY uses the AddHubOptions on the SignalR builder, because it will merge
31+
// the global HubOptions before running the configure callback. We want to ensure that happens
32+
// once. Our AddHubOptions method doesn't do this.
33+
//
34+
// We need to restrict the set of protocols used by default to our specialized one. Users have
35+
// the chance to modify options further via the builder.
36+
//
37+
// Other than the options, the things exposed by the SignalR builder aren't very meaningful in
38+
// the Server-Side Blazor context and thus aren't exposed.
39+
services.AddSignalR().AddHubOptions<ComponentHub>(options =>
3040
{
3141
options.SupportedProtocols.Clear();
3242
options.SupportedProtocols.Add(BlazorPackHubProtocol.ProtocolName);
@@ -52,11 +62,23 @@ public static IServiceCollection AddServerSideBlazor(this IServiceCollection ser
5262
services.AddScoped<IComponentPrerenderer, CircuitPrerenderer>();
5363

5464
// Standard razor component services implementations
65+
//
66+
// These intentionally replace the non-interactive versions included in MVC.
5567
services.AddScoped<IUriHelper, RemoteUriHelper>();
5668
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
5769
services.AddScoped<IComponentContext, RemoteComponentContext>();
5870

59-
return services;
71+
return builder;
72+
}
73+
74+
private class DefaultServerSideBlazorBuilder : IServerSideBlazorBuilder
75+
{
76+
public DefaultServerSideBlazorBuilder(IServiceCollection services)
77+
{
78+
Services = services;
79+
}
80+
81+
public IServiceCollection Services { get; }
6082
}
6183
}
6284
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.Extensions.DependencyInjection
5+
{
6+
/// <summary>
7+
/// A builder that can be used to configure Server-Side Blazor.
8+
/// </summary>
9+
public interface IServerSideBlazorBuilder
10+
{
11+
/// <summary>
12+
/// Gets the <see cref="IServiceCollection"/>.
13+
/// </summary>
14+
IServiceCollection Services { get; }
15+
}
16+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.Components.Server;
6+
using Microsoft.AspNetCore.SignalR;
7+
8+
namespace Microsoft.Extensions.DependencyInjection
9+
{
10+
/// <summary>
11+
/// Provides options for configuring Server-Side Blazor.
12+
/// </summary>
13+
public static class ServerSizeBlazorBuilderExtensions
14+
{
15+
/// <summary>
16+
/// Adds hub options for the configuration of the SignalR Hub used by Server-Side Blazor.
17+
/// </summary>
18+
/// <param name="builder">The <see cref="IServerSideBlazorBuilder"/>.</param>
19+
/// <param name="configure">A callback to configure the hub options.</param>
20+
/// <returns>The <see cref="IServerSideBlazorBuilder"/>.</returns>
21+
public static IServerSideBlazorBuilder AddHubOptions(this IServerSideBlazorBuilder builder, Action<HubOptions> configure)
22+
{
23+
if (builder == null)
24+
{
25+
throw new ArgumentNullException(nameof(builder));
26+
}
27+
28+
if (configure != null)
29+
{
30+
builder.Services.Configure<HubOptions<ComponentHub>>(configure);
31+
}
32+
33+
return builder;
34+
}
35+
}
36+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.Components.Server;
6+
using Microsoft.AspNetCore.Components.Server.BlazorPack;
7+
using Microsoft.AspNetCore.SignalR;
8+
using Microsoft.Extensions.Options;
9+
using Xunit;
10+
11+
namespace Microsoft.Extensions.DependencyInjection
12+
{
13+
public class ComponentServiceCollectionExtensionsTest
14+
{
15+
[Fact]
16+
public void AddServerSideSignalR_RegistersBlazorPack()
17+
{
18+
// Arrange
19+
var services = new ServiceCollection();
20+
services.AddServerSideBlazor();
21+
22+
// Act
23+
var options = services.BuildServiceProvider().GetRequiredService<IOptions<HubOptions<ComponentHub>>>();
24+
25+
// Assert
26+
var protocol = Assert.Single(options.Value.SupportedProtocols);
27+
Assert.Equal(BlazorPackHubProtocol.ProtocolName, protocol);
28+
}
29+
30+
[Fact]
31+
public void AddServerSideSignalR_RespectsGlobalHubOptions()
32+
{
33+
// Arrange
34+
var services = new ServiceCollection();
35+
services.AddServerSideBlazor();
36+
37+
services.Configure<HubOptions>(options =>
38+
{
39+
options.SupportedProtocols.Add("test");
40+
options.HandshakeTimeout = TimeSpan.FromMinutes(10);
41+
});
42+
43+
// Act
44+
var options = services.BuildServiceProvider().GetRequiredService<IOptions<HubOptions<ComponentHub>>>();
45+
46+
// Assert
47+
var protocol = Assert.Single(options.Value.SupportedProtocols);
48+
Assert.Equal(BlazorPackHubProtocol.ProtocolName, protocol);
49+
Assert.Equal(TimeSpan.FromMinutes(10), options.Value.HandshakeTimeout);
50+
}
51+
52+
[Fact]
53+
public void AddServerSideSignalR_ConfiguresGlobalOptionsBeforePerHubOptions()
54+
{
55+
// Arrange
56+
var services = new ServiceCollection();
57+
services.AddServerSideBlazor().AddHubOptions(options =>
58+
{
59+
Assert.Equal(TimeSpan.FromMinutes(10), options.HandshakeTimeout);
60+
options.HandshakeTimeout = TimeSpan.FromMinutes(5);
61+
});
62+
63+
services.Configure<HubOptions>(options =>
64+
{
65+
options.SupportedProtocols.Add("test");
66+
options.HandshakeTimeout = TimeSpan.FromMinutes(10);
67+
});
68+
69+
// Act
70+
var options = services.BuildServiceProvider().GetRequiredService<IOptions<HubOptions<ComponentHub>>>();
71+
var globalOptions = services.BuildServiceProvider().GetRequiredService<IOptions<HubOptions>>();
72+
73+
// Assert
74+
var protocol = Assert.Single(options.Value.SupportedProtocols);
75+
Assert.Equal(BlazorPackHubProtocol.ProtocolName, protocol);
76+
Assert.Equal(TimeSpan.FromMinutes(5), options.Value.HandshakeTimeout);
77+
78+
// Configuring Blazor options is kept separate from the global options.
79+
Assert.Equal(TimeSpan.FromMinutes(10), globalOptions.Value.HandshakeTimeout);
80+
}
81+
}
82+
}

src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515

1616
<ItemGroup>
1717
<Compile Include="..\..\Components\test\Rendering\HtmlRendererTestBase.cs" />
18-
<Compile Include="$(SignalRTestBase)HubMessageHelpers.cs" LinkBase="BlazorPack"/>
19-
<Compile Include="$(SignalRTestBase)MessagePackHubProtocolTestBase.cs" LinkBase="BlazorPack"/>
20-
<Compile Include="$(SignalRTestBase)TestBinder.cs" LinkBase="BlazorPack"/>
21-
<Compile Include="$(SignalRTestBase)TestHubMessageEqualityComparer.cs" LinkBase="BlazorPack"/>
18+
<Compile Include="$(SignalRTestBase)HubMessageHelpers.cs" LinkBase="BlazorPack" />
19+
<Compile Include="$(SignalRTestBase)MessagePackHubProtocolTestBase.cs" LinkBase="BlazorPack" />
20+
<Compile Include="$(SignalRTestBase)TestBinder.cs" LinkBase="BlazorPack" />
21+
<Compile Include="$(SignalRTestBase)TestHubMessageEqualityComparer.cs" LinkBase="BlazorPack" />
2222
</ItemGroup>
2323

2424
</Project>

src/Components/test/testassets/TestServer/Startup.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
5555
{
5656
subdirApp.UseClientSideBlazorFiles<BasicTestApp.Startup>();
5757

58-
// The following two lines are equivalent to:
59-
// endpoints.MapComponentsHub<Index>();
60-
//
61-
// However it's expressed using routing as a way of checking that
62-
// we're not relying on any extra magic inside MapComponentsHub, since it's
63-
// important that people can set up these bits of middleware manually (e.g., to
64-
// swap in UseAzureSignalR instead of UseSignalR).
6558
subdirApp.UseRouting();
6659

6760
subdirApp.UseEndpoints(endpoints =>
6861
{
69-
endpoints.MapHub<ComponentHub>(ComponentHub.DefaultPath).AddComponent(typeof(Index), selector: "root");
62+
endpoints.MapBlazorHub(typeof(Index), selector: "root");
7063
endpoints.MapFallbackToClientSideBlazor<BasicTestApp.Startup>("index.html");
7164
});
7265
});

0 commit comments

Comments
 (0)