Skip to content
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
5 changes: 5 additions & 0 deletions src/Components/Authorization/src/AuthorizeRouteView.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Rendering;
Expand Down Expand Up @@ -95,6 +96,10 @@ private void RenderAuthorizeRouteViewCore(RenderTreeBuilder builder)
builder.CloseComponent();
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111:RequiresUnreferencedCode",
Justification = "OpenComponent already has the right set of attributes")]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2110:RequiresUnreferencedCode",
Justification = "OpenComponent already has the right set of attributes")]
private void RenderContentInDefaultLayout(RenderTreeBuilder builder, RenderFragment content)
{
builder.OpenComponent<LayoutView>(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ public sealed class ComponentEndpointConventionBuilder : IHubEndpointConventionB
{
private readonly IEndpointConventionBuilder _hubEndpoint;
private readonly IEndpointConventionBuilder _disconnectEndpoint;
private readonly IEndpointConventionBuilder _jsInitializersEndpoint;

internal ComponentEndpointConventionBuilder(IEndpointConventionBuilder hubEndpoint, IEndpointConventionBuilder disconnectEndpoint)
internal ComponentEndpointConventionBuilder(IEndpointConventionBuilder hubEndpoint, IEndpointConventionBuilder disconnectEndpoint, IEndpointConventionBuilder jsInitializersEndpoint)
{
_hubEndpoint = hubEndpoint;
_disconnectEndpoint = disconnectEndpoint;
_jsInitializersEndpoint = jsInitializersEndpoint;
}

/// <summary>
Expand All @@ -27,6 +29,7 @@ public void Add(Action<EndpointBuilder> convention)
{
_hubEndpoint.Add(convention);
_disconnectEndpoint.Add(convention);
_jsInitializersEndpoint.Add(convention);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@

using System;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Microsoft.AspNetCore.Builder
{
Expand Down Expand Up @@ -110,7 +113,12 @@ public static ComponentEndpointConventionBuilder MapBlazorHub(
endpoints.CreateApplicationBuilder().UseMiddleware<CircuitDisconnectMiddleware>().Build())
.WithDisplayName("Blazor disconnect");

return new ComponentEndpointConventionBuilder(hubEndpoint, disconnectEndpoint);
var jsInitializersEndpoint = endpoints.Map(
(path.EndsWith('/') ? path : path + "/") + "initializers/",
endpoints.CreateApplicationBuilder().UseMiddleware<CircuitJavaScriptInitializationMiddleware>().Build())
.WithDisplayName("Blazor initializers");

return new ComponentEndpointConventionBuilder(hubEndpoint, disconnectEndpoint, jsInitializersEndpoint);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Builder
{
internal class CircuitJavaScriptInitializationMiddleware
{
private readonly IList<string> _initializers;

// We don't need the request delegate for anything, however we need to inject it to satisfy the middleware
// contract.
public CircuitJavaScriptInitializationMiddleware(IOptions<CircuitOptions> options, RequestDelegate _)
{
_initializers = options.Value.JavaScriptInitializers;
}

public async Task InvokeAsync(HttpContext context)
{
await context.Response.WriteAsJsonAsync(_initializers);
}
}
}
5 changes: 3 additions & 2 deletions src/Components/Server/src/CircuitOptions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.Hosting;

namespace Microsoft.AspNetCore.Components.Server
{
Expand Down Expand Up @@ -82,5 +81,7 @@ public sealed class CircuitOptions
/// Gets options for root components within the circuit.
/// </summary>
public CircuitRootComponentOptions RootComponents { get; } = new CircuitRootComponentOptions();

internal IList<string> JavaScriptInitializers { get; } = new List<string>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Components.Server.Circuits
{
internal class CircuitOptionsJavaScriptInitializersConfiguration : IConfigureOptions<CircuitOptions>
{
private readonly IWebHostEnvironment _environment;

public CircuitOptionsJavaScriptInitializersConfiguration(IWebHostEnvironment environment)
{
_environment = environment;
}

public void Configure(CircuitOptions options)
{
var file = _environment.WebRootFileProvider.GetFileInfo($"{_environment.ApplicationName}.modules.json");
if (file.Exists)
{
var initializers = JsonSerializer.Deserialize<string[]>(file.CreateReadStream());
for (var i = 0; i < initializers.Length; i++)
{
var initializer = initializers[i];
options.JavaScriptInitializers.Add(initializer);
}
}
}
}
}
1 change: 1 addition & 0 deletions src/Components/Server/src/ComponentHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Components.Server
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollecti
services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();

services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CircuitOptions>, CircuitOptionsJSInteropDetailedErrorsConfiguration>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CircuitOptions>, CircuitOptionsJavaScriptInitializersConfiguration>());

if (configure != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Moq;
using Xunit;
Expand Down Expand Up @@ -54,6 +55,9 @@ public void MapBlazorHub_MostGeneralOverload_MapsUnderlyingHub()

private IApplicationBuilder CreateAppBuilder()
{
var environment = new Mock<IWebHostEnvironment>();
environment.SetupGet(e => e.ApplicationName).Returns("app");
environment.SetupGet(e => e.WebRootFileProvider).Returns(new NullFileProvider());
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IHostApplicationLifetime>());
services.AddLogging();
Expand All @@ -64,6 +68,7 @@ private IApplicationBuilder CreateAppBuilder()
services.AddRouting();
services.AddSignalR();
services.AddServerSideBlazor();
services.AddSingleton(environment.Object);
services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());

var serviceProvider = services.BuildServiceProvider();
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.server.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.webview.js

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion src/Components/Web.JS/src/Boot.Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { DefaultReconnectionHandler } from './Platform/Circuits/DefaultReconnect
import { attachRootComponentToLogicalElement } from './Rendering/Renderer';
import { discoverComponents, discoverPersistedState, ServerComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
import { sendJSDataStream } from './Platform/Circuits/CircuitStreamingInterop';
import { fetchAndInvokeInitializers } from './JSInitializers/JSInitializers.Server';

let renderingFailed = false;
let started = false;
Expand All @@ -25,6 +26,8 @@ async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> {

// Establish options to be used
const options = resolveOptions(userOptions);
const jsInitializer = await fetchAndInvokeInitializers(options);

const logger = new ConsoleLogger(options.logLevel);
Blazor.defaultReconnectionHandler = new DefaultReconnectionHandler(logger);

Expand All @@ -35,7 +38,6 @@ async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> {
const appState = discoverPersistedState(document);
const circuit = new CircuitDescriptor(components, appState || '');


const initialConnection = await initializeConnection(options, logger, circuit);
const circuitStarted = await circuit.startCircuit(initialConnection);
if (!circuitStarted) {
Expand Down Expand Up @@ -77,6 +79,8 @@ async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> {
Blazor.reconnect = reconnect;

logger.log(LogLevel.Information, 'Blazor server-side application started.');

jsInitializer.invokeAfterStartedCallbacks(Blazor);
}

async function initializeConnection(options: CircuitStartOptions, logger: Logger, circuit: CircuitDescriptor): Promise<HubConnection> {
Expand Down
19 changes: 14 additions & 5 deletions src/Components/Web.JS/src/Boot.WebAssembly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
import { WebAssemblyComponentAttacher } from './Platform/WebAssemblyComponentAttacher';
import { discoverComponents, discoverPersistedState, WebAssemblyComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
import { setDispatchEventMiddleware } from './Rendering/WebRendererInteropMethods';
import { AfterBlazorStartedCallback, JSInitializer } from './JSInitializers/JSInitializers';
import { fetchAndInvokeInitializers } from './JSInitializers/JSInitializers.WebAssembly';

declare var Module: EmscriptenModule;
let started = false;
Expand Down Expand Up @@ -80,11 +82,13 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
);
});

// Get the custom environment setting if defined
const environment = options?.environment;
const candidateOptions = options ?? {};

// Get the custom environment setting and blazorBootJson loader if defined
const environment = candidateOptions.environment;

// Fetch the resources and prepare the Mono runtime
const bootConfigPromise = BootConfigResult.initAsync(environment);
const bootConfigPromise = BootConfigResult.initAsync(candidateOptions.loadBootResource, environment);

// Leverage the time while we are loading boot.config.json from the network to discover any potentially registered component on
// the document.
Expand All @@ -110,9 +114,11 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
}
};

const bootConfigResult = await bootConfigPromise;
const bootConfigResult: BootConfigResult = await bootConfigPromise;
const jsInitializer = await fetchAndInvokeInitializers(bootConfigResult.bootConfig, candidateOptions);

const [resourceLoader] = await Promise.all([
WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, options || {}),
WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, candidateOptions || {}),
WebAssemblyConfigLoader.initAsync(bootConfigResult)]);

try {
Expand All @@ -123,6 +129,9 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {

// Start up the application
platform.callEntryPoint(resourceLoader.bootConfig.entryAssembly);
// At this point .NET has been initialized (and has yielded), we can't await the promise becasue it will
// only end when the app finishes running
jsInitializer.invokeAfterStartedCallbacks(Blazor);
}

function invokeJSFromDotNet(callInfo: Pointer, arg0: any, arg1: any, arg2: any): any {
Expand Down
4 changes: 4 additions & 0 deletions src/Components/Web.JS/src/Boot.WebView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { shouldAutoStart } from './BootCommon';
import { internalFunctions as navigationManagerFunctions } from './Services/NavigationManager';
import { startIpcReceiver } from './Platform/WebView/WebViewIpcReceiver';
import { sendAttachPage, sendBeginInvokeDotNetFromJS, sendEndInvokeJSFromDotNet, sendByteArray, sendLocationChanged } from './Platform/WebView/WebViewIpcSender';
import { fetchAndInvokeInitializers } from './JSInitializers/JSInitializers.WebView';

let started = false;

Expand All @@ -13,6 +14,8 @@ async function boot(): Promise<void> {
}
started = true;

const jsInitializer = await fetchAndInvokeInitializers();

startIpcReceiver();

DotNet.attachDispatcher({
Expand All @@ -25,6 +28,7 @@ async function boot(): Promise<void> {
navigationManagerFunctions.listenForNavigationEvents(sendLocationChanged);

sendAttachPage(navigationManagerFunctions.getBaseURI(), navigationManagerFunctions.getLocationHref());
await jsInitializer.invokeAfterStartedCallbacks(Blazor);
}

Blazor.start = boot;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BootJsonData } from "../Platform/BootConfig";
import { CircuitStartOptions } from "../Platform/Circuits/CircuitStartOptions";
import { JSInitializer } from "./JSInitializers";

export async function fetchAndInvokeInitializers(options: Partial<CircuitStartOptions>) : Promise<JSInitializer> {
const jsInitializersResponse = await fetch('_blazor/initializers', {
method: 'GET',
credentials: 'include',
cache: 'no-cache'
});

const initializers: string[] = await jsInitializersResponse.json();
const jsInitializer = new JSInitializer();
await jsInitializer.importInitializersAsync(initializers, [options]);
return jsInitializer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { BootJsonData } from "../Platform/BootConfig";
import { WebAssemblyStartOptions } from "../Platform/WebAssemblyStartOptions";
import { JSInitializer } from "./JSInitializers";

export async function fetchAndInvokeInitializers(bootConfig: BootJsonData, options: Partial<WebAssemblyStartOptions>) : Promise<JSInitializer> {
const initializers = bootConfig.resources.libraryInitializers;
const jsInitializer = new JSInitializer();
if (initializers) {
await jsInitializer.importInitializersAsync(
Object.keys(initializers),
[options, bootConfig.resources.extensions]);
}

return jsInitializer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { JSInitializer } from "./JSInitializers";

export async function fetchAndInvokeInitializers() : Promise<JSInitializer> {
const jsInitializersResponse = await fetch('_framework/blazor.modules.json', {
method: 'GET',
credentials: 'include',
cache: 'no-cache'
});

const initializers: string[] = await jsInitializersResponse.json();
const jsInitializer = new JSInitializer();
await jsInitializer.importInitializersAsync(initializers, []);
return jsInitializer;
}
40 changes: 40 additions & 0 deletions src/Components/Web.JS/src/JSInitializers/JSInitializers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Blazor } from '../GlobalExports';

type BeforeBlazorStartedCallback = (...args: unknown[]) => Promise<void>;
export type AfterBlazorStartedCallback = (blazor: typeof Blazor) => Promise<void>;
type BlazorInitializer = { beforeStart: BeforeBlazorStartedCallback, afterStarted: AfterBlazorStartedCallback };

export class JSInitializer {
private afterStartedCallbacks: AfterBlazorStartedCallback[] = [];

async importInitializersAsync(initializerFiles: string[], initializerArguments: unknown[]): Promise<void> {
await Promise.all(initializerFiles.map(f => importAndInvokeInitializer(this, f)));

function adjustPath(path: string): string {
// This is the same we do in JS interop with the import callback
const base = document.baseURI;
path = base.endsWith('/') ? `${base}${path}` : `${base}/${path}`;
return path;
}

async function importAndInvokeInitializer(jsInitializer: JSInitializer, path: string): Promise<void> {
const adjustedPath = adjustPath(path);
const initializer = await import(/* webpackIgnore: true */ adjustedPath) as Partial<BlazorInitializer>;
if (initializer === undefined) {
return;
}
const { beforeStart: beforeStart, afterStarted: afterStarted } = initializer;
if (afterStarted) {
jsInitializer.afterStartedCallbacks.push(afterStarted);
}

if (beforeStart) {
return beforeStart(...initializerArguments);
}
}
}

async invokeAfterStartedCallbacks(blazor: typeof Blazor) {
await Promise.all(this.afterStartedCallbacks.map(callback => callback(blazor)));
}
}
Loading