Refactor server-side blazor startup to allow Azure SignalR. Fixes #1227 (#1396)

* Move startup action config into AddServerSideBlazor, so that UseServerSideBlazor is reduced to trivial shorthand that can become optional

* Make BlazorHub public so developers can use it with UseAzureSignalR

* Move BlazorHub to Microsoft.AspNetCore.Blazor.Server namespace for easier consumption

* Add notes

* Have E2E tests validate that devs don't have to call UseServerSideBlazor

* Add forgotten tweak

* CR: Document that BlazorHub methods are not intended for application use.

* CR: Rename extension method to UseSignalRWithBlazorHub

* CR: TryAdd
This commit is contained in:
Steve Sanderson 2018-09-11 09:55:27 +01:00 committed by GitHub
parent d4cbb86f46
commit 6ff3674b16
8 changed files with 163 additions and 192 deletions

View File

@ -3,40 +3,59 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Blazor.Server.Circuits;
using Microsoft.AspNetCore.Blazor.Services; using Microsoft.AspNetCore.Blazor.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Blazor.Server.Circuits namespace Microsoft.AspNetCore.Blazor.Server
{ {
internal class BlazorHub : Hub /// <summary>
/// A SignalR hub that accepts connections to a Server-Side Blazor app.
/// </summary>
public sealed class BlazorHub : Hub
{ {
private static readonly object CircuitKey = new object(); private static readonly object CircuitKey = new object();
private readonly CircuitFactory _circuitFactory; private readonly CircuitFactory _circuitFactory;
private readonly ILogger _logger; private readonly ILogger _logger;
/// <summary>
/// Intended for framework use only. Applications should not instantiate
/// this class directly.
/// </summary>
public BlazorHub( public BlazorHub(
ILogger<BlazorHub> logger, ILogger<BlazorHub> logger,
CircuitFactory circuitFactory) IServiceProvider services)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_circuitFactory = circuitFactory ?? throw new ArgumentNullException(nameof(circuitFactory)); _circuitFactory = services.GetRequiredService<CircuitFactory>();
} }
public CircuitHost CircuitHost /// <summary>
/// Gets the default endpoint path for incoming connections.
/// </summary>
public static PathString DefaultPath => "/_blazor";
private CircuitHost CircuitHost
{ {
get => (CircuitHost)Context.Items[CircuitKey]; get => (CircuitHost)Context.Items[CircuitKey];
set => Context.Items[CircuitKey] = value; set => Context.Items[CircuitKey] = value;
} }
/// <summary>
/// Intended for framework use only. Applications should not call this method directly.
/// </summary>
public override Task OnDisconnectedAsync(Exception exception) public override Task OnDisconnectedAsync(Exception exception)
{ {
CircuitHost.Dispose(); CircuitHost.Dispose();
return base.OnDisconnectedAsync(exception); return base.OnDisconnectedAsync(exception);
} }
/// <summary>
/// Intended for framework use only. Applications should not call this method directly.
/// </summary>
public async Task StartCircuit(string uriAbsolute, string baseUriAbsolute) public async Task StartCircuit(string uriAbsolute, string baseUriAbsolute)
{ {
var circuitHost = _circuitFactory.CreateCircuitHost(Context.GetHttpContext(), Clients.Caller); var circuitHost = _circuitFactory.CreateCircuitHost(Context.GetHttpContext(), Clients.Caller);
@ -50,7 +69,10 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
await circuitHost.InitializeAsync(); await circuitHost.InitializeAsync();
CircuitHost = circuitHost; CircuitHost = circuitHost;
} }
/// <summary>
/// Intended for framework use only. Applications should not call this method directly.
/// </summary>
public void BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) public void BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
{ {
EnsureCircuitHost().BeginInvokeDotNetFromJS(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); EnsureCircuitHost().BeginInvokeDotNetFromJS(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);

View File

@ -24,12 +24,13 @@ namespace Microsoft.AspNetCore.Builder
/// Configures the middleware pipeline to work with Blazor. /// Configures the middleware pipeline to work with Blazor.
/// </summary> /// </summary>
/// <typeparam name="TProgram">Any type from the client app project. This is used to identify the client app assembly.</typeparam> /// <typeparam name="TProgram">Any type from the client app project. This is used to identify the client app assembly.</typeparam>
/// <param name="app"></param> /// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
public static void UseBlazor<TProgram>( /// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseBlazor<TProgram>(
this IApplicationBuilder app) this IApplicationBuilder app)
{ {
var clientAssemblyInServerBinDir = typeof(TProgram).Assembly; var clientAssemblyInServerBinDir = typeof(TProgram).Assembly;
app.UseBlazor(new BlazorOptions return app.UseBlazor(new BlazorOptions
{ {
ClientAssemblyPath = clientAssemblyInServerBinDir.Location, ClientAssemblyPath = clientAssemblyInServerBinDir.Location,
}); });
@ -38,9 +39,10 @@ namespace Microsoft.AspNetCore.Builder
/// <summary> /// <summary>
/// Configures the middleware pipeline to work with Blazor. /// Configures the middleware pipeline to work with Blazor.
/// </summary> /// </summary>
/// <param name="app"></param> /// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="options"></param> /// <param name="options">Options to configure the middleware.</param>
public static void UseBlazor( /// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseBlazor(
this IApplicationBuilder app, this IApplicationBuilder app,
BlazorOptions options) BlazorOptions options)
{ {
@ -108,6 +110,8 @@ namespace Microsoft.AspNetCore.Builder
spa.Options.DefaultPageStaticFileOptions = indexHtmlStaticFileOptions; spa.Options.DefaultPageStaticFileOptions = indexHtmlStaticFileOptions;
}); });
}); });
return app;
} }
private static string FindIndexHtmlFile(BlazorConfig config) private static string FindIndexHtmlFile(BlazorConfig config)

View File

@ -2,15 +2,14 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Blazor.Builder; using Microsoft.AspNetCore.Blazor.Server;
using Microsoft.AspNetCore.Blazor.Hosting; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Blazor.Server.Circuits;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Builder namespace Microsoft.AspNetCore.Builder
{ {
/// <summary> /// <summary>
/// Extension methods to configure an <see cref="IApplicationBuilder"/> for Server-Side Blazor. /// Extension methods to configure an <see cref="IApplicationBuilder"/> for Server-Side Blazor.
/// These are just shorthand for combining UseSignalR with UseBlazor.
/// </summary> /// </summary>
public static class ServerSideBlazorApplicationBuilderExtensions public static class ServerSideBlazorApplicationBuilderExtensions
{ {
@ -20,94 +19,58 @@ namespace Microsoft.AspNetCore.Builder
/// <param name="builder">The <see cref="IApplicationBuilder"/>.</param> /// <param name="builder">The <see cref="IApplicationBuilder"/>.</param>
/// <typeparam name="TStartup">A Blazor startup type.</typeparam> /// <typeparam name="TStartup">A Blazor startup type.</typeparam>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns> /// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseServerSideBlazor<TStartup>(this IApplicationBuilder builder) public static IApplicationBuilder UseServerSideBlazor<TStartup>(
this IApplicationBuilder builder)
{ {
if (builder == null) if (builder == null)
{ {
throw new ArgumentNullException(nameof(builder)); throw new ArgumentNullException(nameof(builder));
} }
return UseServerSideBlazor(builder, typeof(TStartup)); // WARNING: Don't add extra setup logic here. It's important for
// UseServerSideBlazor just to be shorthand for UseSignalR+UseBlazor,
// so that people who want to call those two manually instead can
// also do so. That's needed for people using Azure SignalR.
// TODO: Also allow configuring the endpoint path.
return UseSignalRWithBlazorHub(builder, BlazorHub.DefaultPath)
.UseBlazor<TStartup>();
} }
/// <summary> /// <summary>
/// Registers Server-Side Blazor in the pipeline. /// Registers Server-Side Blazor in the pipeline.
/// </summary> /// </summary>
/// <param name="builder">The <see cref="IApplicationBuilder"/>.</param> /// <param name="builder">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="startupType">A Blazor startup type.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseServerSideBlazor(
this IApplicationBuilder builder,
Type startupType)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (startupType == null)
{
throw new ArgumentNullException(nameof(startupType));
}
var startup = builder.ApplicationServices.GetRequiredService(startupType);
var wrapper = new ConventionBasedStartup(startup);
Action<IBlazorApplicationBuilder> configure = (b) =>
{
wrapper.Configure(b, b.Services);
};
UseServerSideBlazorCore(builder, configure);
builder.UseBlazor(new BlazorOptions()
{
ClientAssemblyPath = startupType.Assembly.Location,
});
return builder;
}
/// <summary>
/// Registers middleware for Server-Side Blazor.
/// </summary>
/// <param name="builder">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="options">A <see cref="BlazorOptions"/> instance used to configure the Blazor file provider.</param> /// <param name="options">A <see cref="BlazorOptions"/> instance used to configure the Blazor file provider.</param>
/// <param name="startupAction">A delegate used to configure the renderer.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns> /// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseServerSideBlazor( public static IApplicationBuilder UseServerSideBlazor(
this IApplicationBuilder builder, this IApplicationBuilder builder,
BlazorOptions options, BlazorOptions options)
Action<IBlazorApplicationBuilder> startupAction)
{ {
if (builder == null) if (builder == null)
{ {
throw new ArgumentNullException(nameof(builder)); throw new ArgumentNullException(nameof(builder));
} }
if (startupAction == null) if (options == null)
{ {
throw new ArgumentNullException(nameof(startupAction)); throw new ArgumentNullException(nameof(options));
} }
UseServerSideBlazorCore(builder, startupAction); // WARNING: Don't add extra setup logic here. It's important for
// UseServerSideBlazor just to be shorthand for UseSignalR+UseBlazor,
// so that people who want to call those two manually instead can
// also do so. That's needed for people using Azure SignalR.
builder.UseBlazor(options); // TODO: Also allow configuring the endpoint path.
return UseSignalRWithBlazorHub(builder, BlazorHub.DefaultPath)
return builder; .UseBlazor(options);
} }
private static IApplicationBuilder UseServerSideBlazorCore( private static IApplicationBuilder UseSignalRWithBlazorHub(
IApplicationBuilder builder, IApplicationBuilder builder, PathString path)
Action<IBlazorApplicationBuilder> configure)
{ {
var endpoint = "/_blazor"; return builder.UseSignalR(route => route.MapHub<BlazorHub>(BlazorHub.DefaultPath));
var factory = (DefaultCircuitFactory)builder.ApplicationServices.GetRequiredService<CircuitFactory>();
factory.StartupActions.Add(endpoint, configure);
builder.UseSignalR(route => route.MapHub<BlazorHub>(endpoint));
return builder;
} }
} }
} }

View File

@ -2,32 +2,36 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Blazor.Browser.Rendering; using Microsoft.AspNetCore.Blazor.Browser.Rendering;
using Microsoft.AspNetCore.Blazor.Builder;
using Microsoft.AspNetCore.Blazor.Rendering; using Microsoft.AspNetCore.Blazor.Rendering;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Blazor.Server.Circuits namespace Microsoft.AspNetCore.Blazor.Server.Circuits
{ {
internal class DefaultCircuitFactory : CircuitFactory internal class DefaultCircuitFactory : CircuitFactory
{ {
private readonly IServiceScopeFactory _scopeFactory; private readonly IServiceScopeFactory _scopeFactory;
private readonly DefaultCircuitFactoryOptions _options;
public DefaultCircuitFactory(IServiceScopeFactory scopeFactory) public DefaultCircuitFactory(
IServiceScopeFactory scopeFactory,
IOptions<DefaultCircuitFactoryOptions> options)
{ {
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory)); _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
_options = options.Value;
StartupActions = new Dictionary<PathString, Action<IBlazorApplicationBuilder>>();
} }
public Dictionary<PathString, Action<IBlazorApplicationBuilder>> StartupActions { get; }
public override CircuitHost CreateCircuitHost(HttpContext httpContext, IClientProxy client) public override CircuitHost CreateCircuitHost(HttpContext httpContext, IClientProxy client)
{ {
if (!StartupActions.TryGetValue(httpContext.Request.Path, out var config)) if (!_options.StartupActions.TryGetValue(httpContext.Request.Path, out var config))
{ {
var message = $"Could not find a Blazor startup action for request path {httpContext.Request.Path}"; var message = $"Could not find a Blazor startup action for request path {httpContext.Request.Path}";
throw new InvalidOperationException(message); throw new InvalidOperationException(message);

View File

@ -0,0 +1,22 @@
// 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 Microsoft.AspNetCore.Blazor.Builder;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Blazor.Server.Circuits
{
internal class DefaultCircuitFactoryOptions
{
// During the DI configuration phase, we use Configure<DefaultCircuitFactoryOptions>(...)
// callbacks to build up this dictionary mapping paths to startup actions
public Dictionary<PathString, Action<IBlazorApplicationBuilder>> StartupActions { get; }
public DefaultCircuitFactoryOptions()
{
StartupActions = new Dictionary<PathString, Action<IBlazorApplicationBuilder>>();
}
}
}

View File

@ -2,10 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Blazor;
using Microsoft.AspNetCore.Blazor.Hosting; using Microsoft.AspNetCore.Blazor.Hosting;
using Microsoft.AspNetCore.Blazor.Server;
using Microsoft.AspNetCore.Blazor.Server.Circuits; using Microsoft.AspNetCore.Blazor.Server.Circuits;
using Microsoft.AspNetCore.Blazor.Services; using Microsoft.AspNetCore.Blazor.Services;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.JSInterop; using Microsoft.JSInterop;
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
@ -20,32 +21,15 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary> /// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param> /// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns> /// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddServerSideBlazor(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
return AddServerSideBlazor(services, null, null);
}
/// <summary>
/// Adds Server-Side Blazor services to the service collection.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">A delegate to configure the <see cref="ServerSideBlazorOptions"/>.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddServerSideBlazor( public static IServiceCollection AddServerSideBlazor(
this IServiceCollection services, this IServiceCollection services)
Action<ServerSideBlazorOptions> configure)
{ {
if (services == null) if (services == null)
{ {
throw new ArgumentNullException(nameof(services)); throw new ArgumentNullException(nameof(services));
} }
return AddServerSideBlazorCore(services, null, configure); return AddServerSideBlazor(services, null);
} }
/// <summary> /// <summary>
@ -54,7 +38,9 @@ namespace Microsoft.Extensions.DependencyInjection
/// <param name="services">The <see cref="IServiceCollection"/>.</param> /// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="startupType">A Blazor startup type.</param> /// <param name="startupType">A Blazor startup type.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns> /// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddServerSideBlazor(this IServiceCollection services, Type startupType) public static IServiceCollection AddServerSideBlazor(
this IServiceCollection services,
Type startupType)
{ {
if (services == null) if (services == null)
{ {
@ -66,7 +52,7 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(startupType)); throw new ArgumentNullException(nameof(startupType));
} }
return AddServerSideBlazorCore(services, startupType, null); return AddServerSideBlazorCore(services, startupType);
} }
/// <summary> /// <summary>
@ -75,101 +61,75 @@ namespace Microsoft.Extensions.DependencyInjection
/// <param name="services">The <see cref="IServiceCollection"/>.</param> /// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <typeparam name="TStartup">A Blazor startup type.</typeparam> /// <typeparam name="TStartup">A Blazor startup type.</typeparam>
/// <returns>The <see cref="IServiceCollection"/>.</returns> /// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddServerSideBlazor<TStartup>(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
return AddServerSideBlazorCore(services, typeof(TStartup), null);
}
/// <summary>
/// Adds Server-Side Blazor services to the service collection.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="startupType">A Blazor startup type.</param>
/// <param name="configure">A delegate to configure the <see cref="ServerSideBlazorOptions"/>.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddServerSideBlazor(
this IServiceCollection services,
Type startupType,
Action<ServerSideBlazorOptions> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (startupType == null)
{
throw new ArgumentNullException(nameof(startupType));
}
return AddServerSideBlazorCore(services, startupType, configure);
}
/// <summary>
/// Adds Server-Side Blazor services to the service collection.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">A delegate to configure the <see cref="ServerSideBlazorOptions"/>.</param>
/// <typeparam name="TStartup">A Blazor startup type.</typeparam>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddServerSideBlazor<TStartup>( public static IServiceCollection AddServerSideBlazor<TStartup>(
this IServiceCollection services, this IServiceCollection services)
Action<ServerSideBlazorOptions> configure)
{ {
if (services == null) if (services == null)
{ {
throw new ArgumentNullException(nameof(services)); throw new ArgumentNullException(nameof(services));
} }
return AddServerSideBlazorCore(services, typeof(TStartup), configure); return AddServerSideBlazorCore(services, typeof(TStartup));
} }
private static IServiceCollection AddServerSideBlazorCore( private static IServiceCollection AddServerSideBlazorCore(
IServiceCollection services, IServiceCollection services,
Type startupType, Type startupType)
Action<ServerSideBlazorOptions> configure)
{ {
services.AddSingleton<CircuitFactory, DefaultCircuitFactory>(); AddStandardServerSideBlazorServices(services);
services.AddScoped<ICircuitAccessor, DefaultCircuitAccessor>();
services.AddScoped<Circuit>(s => s.GetRequiredService<ICircuitAccessor>().Circuit);
services.AddScoped<IJSRuntimeAccessor, DefaultJSRuntimeAccessor>();
services.AddScoped<IJSRuntime>(s => s.GetRequiredService<IJSRuntimeAccessor>().JSRuntime);
services.AddScoped<IUriHelper, RemoteUriHelper>();
services.AddSignalR().AddMessagePackProtocol(options =>
{
// TODO: Enable compression, either by having SignalR use
// LZ4MessagePackSerializer instead of MessagePackSerializer,
// or perhaps by compressing the RenderBatch bytes ourselves
// and then using https://github.com/nodeca/pako in JS to decompress.
options.FormatterResolvers.Insert(0, new RenderBatchFormatterResolver());
});
if (startupType != null) if (startupType != null)
{ {
// Make sure we only create a single instance of the startup type. We can register // Call TStartup's ConfigureServices method immediately
// it in the services so we can retrieve it later when creating the middlware.
var startup = Activator.CreateInstance(startupType); var startup = Activator.CreateInstance(startupType);
services.AddSingleton(startupType, startup);
// We don't need to reuse the wrapper, it's not stateful.
var wrapper = new ConventionBasedStartup(startup); var wrapper = new ConventionBasedStartup(startup);
wrapper.ConfigureServices(services); wrapper.ConfigureServices(services);
}
if (configure != null) // Configure the circuit factory to call a startup action when each
{ // incoming connection is established. The startup action is "call
services.Configure(configure); // TStartup's Configure method".
services.Configure<DefaultCircuitFactoryOptions>(circuitFactoryOptions =>
{
var endpoint = BlazorHub.DefaultPath; // TODO: allow configuring this
if (circuitFactoryOptions.StartupActions.ContainsKey(endpoint))
{
throw new InvalidOperationException(
"Multiple Server Side Blazor entries are configured to use " +
$"the same endpoint '{endpoint}'.");
}
circuitFactoryOptions.StartupActions.Add(endpoint, builder =>
{
wrapper.Configure(builder, builder.Services);
});
});
} }
return services; return services;
} }
private static void AddStandardServerSideBlazorServices(IServiceCollection services)
{
// Here we add a bunch of services that don't vary in any way based on the
// user's configuration. So even if the user has multiple independent server-side
// Blazor entrypoints, this lot is the same and repeated registrations are a no-op.
services.TryAddSingleton<CircuitFactory, DefaultCircuitFactory>();
services.TryAddScoped<ICircuitAccessor, DefaultCircuitAccessor>();
services.TryAddScoped<Circuit>(s => s.GetRequiredService<ICircuitAccessor>().Circuit);
services.TryAddScoped<IJSRuntimeAccessor, DefaultJSRuntimeAccessor>();
services.TryAddScoped<IJSRuntime>(s => s.GetRequiredService<IJSRuntimeAccessor>().JSRuntime);
services.TryAddScoped<IUriHelper, RemoteUriHelper>();
// We've discussed with the SignalR team and believe it's OK to have repeated
// calls to AddSignalR (making the nonfirst ones no-ops). If we want to change
// this in the future, we could change AddServerSideBlazor to be an extension
// method on ISignalRServerBuilder so the developer always has to chain it onto
// their own AddSignalR call. For now we're keeping it like this because it's
// simpler for developers in common cases.
services.AddSignalR().AddMessagePackProtocol(options =>
{
options.FormatterResolvers.Insert(0, new RenderBatchFormatterResolver());
});
}
} }
} }

View File

@ -1,12 +0,0 @@
// 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.Blazor
{
/// <summary>
/// Options for Server-Side Blazor.
/// </summary>
public class ServerSideBlazorOptions
{
}
}

View File

@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Blazor.Server;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -39,7 +40,14 @@ namespace TestServer
// Mount the server-side Blazor app on /subdir // Mount the server-side Blazor app on /subdir
app.Map("/subdir", subdirApp => app.Map("/subdir", subdirApp =>
{ {
subdirApp.UseServerSideBlazor<BasicTestApp.Startup>(); // The following two lines are equivalent to:
// subdirApp.UseServerSideBlazor<BasicTestApp.Startup>();
// However it's expressed using UseSignalR+UseBlazor as a way of checking that
// we're not relying on any extra magic inside UseServerSideBlazor, since it's
// important that people can set up these bits of middleware manually (e.g., to
// swap in UseAzureSignalR instead of UseSignalR).
subdirApp.UseSignalR(route => route.MapHub<BlazorHub>(BlazorHub.DefaultPath));
subdirApp.UseBlazor<BasicTestApp.Startup>();
}); });
} }