Skip to content

Fix #9316 Expose Blazor HubOptions #9998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public static partial class ComponentEndpointConventionBuilderExtensions
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<TComponent>(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string selector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
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; }
Expand All @@ -35,17 +36,6 @@ public sealed partial class ComponentEndpointConventionBuilder : Microsoft.AspNe
internal ComponentEndpointConventionBuilder() { }
public void Add(System.Action<Microsoft.AspNetCore.Builder.EndpointBuilder> convention) { }
}
public sealed partial class ComponentHub : Microsoft.AspNetCore.SignalR.Hub
{
public ComponentHub(System.IServiceProvider services, Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Components.Server.ComponentHub> 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<bool> 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() { }
Expand Down Expand Up @@ -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<Microsoft.AspNetCore.SignalR.HubOptions> configure) { throw null; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.SignalR;

namespace Microsoft.AspNetCore.Builder
{
Expand Down Expand Up @@ -54,6 +53,37 @@ public static ComponentEndpointConventionBuilder MapBlazorHub<TComponent>(
return endpoints.MapBlazorHub(typeof(TComponent), selector, ComponentHub.DefaultPath);
}

/// <summary>
/// Maps the SignalR <see cref="ComponentHub"/> to the path <see cref="ComponentHub.DefaultPath"/> and associates
/// the component <paramref name="type"/> to this hub instance as the given DOM <paramref name="selector"/>.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
/// <param name="type">The first <see cref="IComponent"/> associated with this <see cref="ComponentHub"/>.</param>
/// <param name="selector">The selector for the component.</param>
/// <returns>The <see cref="ComponentEndpointConventionBuilder"/>.</returns>
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);
}

/// <summary>
/// Maps the SignalR <see cref="ComponentHub"/> to the path <paramref name="path"/> and associates
/// the component <typeparamref name="TComponent"/> to this hub instance as the given DOM <paramref name="selector"/>.
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Server/src/ComponentHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Components.Server
/// <summary>
/// A SignalR hub that accepts connections to an ASP.NET Core Components application.
/// </summary>
public sealed class ComponentHub : Hub
internal sealed class ComponentHub : Hub
{
private static readonly object CircuitKey = new object();
private readonly CircuitFactory _circuitFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,24 @@ namespace Microsoft.Extensions.DependencyInjection
public static class ComponentServiceCollectionExtensions
{
/// <summary>
/// Adds Razor Component app services to the service collection.
/// Adds Server-Side Blazor services to the service collection.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddServerSideBlazor(this IServiceCollection services)
/// <returns>An <see cref="IServerSideBlazorBuilder"/> that can be used to further customize the configuration.</returns>
public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollection services)
{
services.AddSignalR()
.AddHubOptions<ComponentHub>(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<ComponentHub>(options =>
{
options.SupportedProtocols.Clear();
options.SupportedProtocols.Add(BlazorPackHubProtocol.ProtocolName);
Expand All @@ -52,11 +62,23 @@ public static IServiceCollection AddServerSideBlazor(this IServiceCollection ser
services.AddScoped<IComponentPrerenderer, CircuitPrerenderer>();

// Standard razor component services implementations
//
// These intentionally replace the non-interactive versions included in MVC.
services.AddScoped<IUriHelper, RemoteUriHelper>();
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
services.AddScoped<IComponentContext, RemoteComponentContext>();

return services;
return builder;
}

private class DefaultServerSideBlazorBuilder : IServerSideBlazorBuilder
{
public DefaultServerSideBlazorBuilder(IServiceCollection services)
{
Services = services;
}

public IServiceCollection Services { get; }
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// A builder that can be used to configure Server-Side Blazor.
/// </summary>
public interface IServerSideBlazorBuilder
{
/// <summary>
/// Gets the <see cref="IServiceCollection"/>.
/// </summary>
IServiceCollection Services { get; }
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Provides options for configuring Server-Side Blazor.
/// </summary>
public static class ServerSizeBlazorBuilderExtensions
{
/// <summary>
/// Adds hub options for the configuration of the SignalR Hub used by Server-Side Blazor.
/// </summary>
/// <param name="builder">The <see cref="IServerSideBlazorBuilder"/>.</param>
/// <param name="configure">A callback to configure the hub options.</param>
/// <returns>The <see cref="IServerSideBlazorBuilder"/>.</returns>
public static IServerSideBlazorBuilder AddHubOptions(this IServerSideBlazorBuilder builder, Action<HubOptions> configure)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}

if (configure != null)
{
builder.Services.Configure<HubOptions<ComponentHub>>(configure);
}

return builder;
}
}
}
Original file line number Diff line number Diff line change
@@ -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<IOptions<HubOptions<ComponentHub>>>();

// 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<HubOptions>(options =>
{
options.SupportedProtocols.Add("test");
options.HandshakeTimeout = TimeSpan.FromMinutes(10);
});

// Act
var options = services.BuildServiceProvider().GetRequiredService<IOptions<HubOptions<ComponentHub>>>();

// 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<HubOptions>(options =>
{
options.SupportedProtocols.Add("test");
options.HandshakeTimeout = TimeSpan.FromMinutes(10);
});

// Act
var options = services.BuildServiceProvider().GetRequiredService<IOptions<HubOptions<ComponentHub>>>();
var globalOptions = services.BuildServiceProvider().GetRequiredService<IOptions<HubOptions>>();

// 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@

<ItemGroup>
<Compile Include="..\..\Components\test\Rendering\HtmlRendererTestBase.cs" />
<Compile Include="$(SignalRTestBase)HubMessageHelpers.cs" LinkBase="BlazorPack"/>
<Compile Include="$(SignalRTestBase)MessagePackHubProtocolTestBase.cs" LinkBase="BlazorPack"/>
<Compile Include="$(SignalRTestBase)TestBinder.cs" LinkBase="BlazorPack"/>
<Compile Include="$(SignalRTestBase)TestHubMessageEqualityComparer.cs" LinkBase="BlazorPack"/>
<Compile Include="$(SignalRTestBase)HubMessageHelpers.cs" LinkBase="BlazorPack" />
<Compile Include="$(SignalRTestBase)MessagePackHubProtocolTestBase.cs" LinkBase="BlazorPack" />
<Compile Include="$(SignalRTestBase)TestBinder.cs" LinkBase="BlazorPack" />
<Compile Include="$(SignalRTestBase)TestHubMessageEqualityComparer.cs" LinkBase="BlazorPack" />
</ItemGroup>

</Project>
9 changes: 1 addition & 8 deletions src/Components/test/testassets/TestServer/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
subdirApp.UseClientSideBlazorFiles<BasicTestApp.Startup>();

// The following two lines are equivalent to:
// endpoints.MapComponentsHub<Index>();
//
// 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>(ComponentHub.DefaultPath).AddComponent(typeof(Index), selector: "root");
endpoints.MapBlazorHub(typeof(Index), selector: "root");
endpoints.MapFallbackToClientSideBlazor<BasicTestApp.Startup>("index.html");
});
});
Expand Down