Fix #9316 Expose Blazor HubOptions

This commit is contained in:
Ryan Nowak 2019-05-06 08:59:53 -07:00
parent 16c01d56e2
commit b3836954ed
9 changed files with 209 additions and 32 deletions

View File

@ -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<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; }
@ -35,17 +36,6 @@ namespace Microsoft.AspNetCore.Components.Server
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() { }
@ -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; }
}
}

View File

@ -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);
}
/// <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"/>.

View File

@ -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;

View File

@ -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);
@ -52,11 +62,23 @@ namespace Microsoft.Extensions.DependencyInjection
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; }
}
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -55,18 +55,11 @@ namespace TestServer
{
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");
});
});