From 3ac126eba11e064b1ecd403ff96b2bae21bb9bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 28 Apr 2023 14:47:57 +0200 Subject: [PATCH 1/9] Load appSettings to vfs --- .../runtime/blazor/WebAssemblyConfigLoader.ts | 42 +++++++++++++++++++ src/mono/wasm/runtime/blazor/_Integration.ts | 17 ++++++++ src/mono/wasm/runtime/startup.ts | 7 +++- 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 src/mono/wasm/runtime/blazor/WebAssemblyConfigLoader.ts diff --git a/src/mono/wasm/runtime/blazor/WebAssemblyConfigLoader.ts b/src/mono/wasm/runtime/blazor/WebAssemblyConfigLoader.ts new file mode 100644 index 00000000000000..1a98522bb6962e --- /dev/null +++ b/src/mono/wasm/runtime/blazor/WebAssemblyConfigLoader.ts @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { BootJsonData } from "./BootConfig"; +import { WebAssemblyStartOptions } from "./WebAssemblyStartOptions"; + +export type ConfigFile = { + name: string, + content: Uint8Array +} + +export class WebAssemblyConfigLoader { + static async initAsync(bootConfig: BootJsonData, applicationEnvironment: string, startOptions: Partial): Promise> { + const configFiles = await Promise.all((bootConfig.config || []) + .filter(name => name === "appsettings.json" || name === `appsettings.${applicationEnvironment}.json`) + .map(async name => ({ name, content: await getConfigBytes(name) }))); + + async function getConfigBytes(file: string): Promise { + // Allow developers to override how the config is loaded + if (startOptions.loadBootResource) { + const customLoadResult = startOptions.loadBootResource("configuration", file, file, ""); + if (customLoadResult instanceof Promise) { + // They are supplying an entire custom response, so just use that + return new Uint8Array(await (await customLoadResult).arrayBuffer()); + } else if (typeof customLoadResult === "string") { + // They are supplying a custom URL, so use that with the default fetch behavior + file = customLoadResult; + } + } + + const response = await fetch(file, { + method: "GET", + credentials: "include", + cache: "no-cache", + }); + + return new Uint8Array(await response.arrayBuffer()); + } + + return configFiles; + } +} diff --git a/src/mono/wasm/runtime/blazor/_Integration.ts b/src/mono/wasm/runtime/blazor/_Integration.ts index 7518796505091c..faf3847772adfe 100644 --- a/src/mono/wasm/runtime/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/blazor/_Integration.ts @@ -1,6 +1,7 @@ import { INTERNAL, Module } from "../imports"; import { AssetEntry, LoadingResource, MonoConfigInternal } from "../types"; import { BootConfigResult, BootJsonData, ICUDataMode } from "./BootConfig"; +import { WebAssemblyConfigLoader } from "./WebAssemblyConfigLoader"; import { WebAssemblyResourceLoader } from "./WebAssemblyResourceLoader"; import { WebAssemblyBootResourceType } from "./WebAssemblyStartOptions"; import { hasDebuggingEnabled } from "./_Polyfill"; @@ -20,6 +21,22 @@ export async function loadBootConfig(config: MonoConfigInternal,) { Module.config = newConfig; } +export async function loadConfigFilesToVfs(resourceLoader: WebAssemblyResourceLoader | null, config: MonoConfigInternal) { + if (!resourceLoader) { + return; + } + + const configFiles = await WebAssemblyConfigLoader.initAsync(resourceLoader.bootConfig, config.applicationEnvironment!, config.startupOptions ?? {}); + for (let i = 0; i < configFiles.length; i++) { + const configFile = configFiles[i]; + Module.FS_createDataFile( + "/", // TODO: Maybe working directory? + configFile.name, + configFile.content, true /* canRead */, true /* canWrite */, true /* canOwn */ + ); + } +} + let resourcesLoaded = 0; let totalResources = 0; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 92c644c64a182b..c37bc48dcfbd68 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -26,7 +26,7 @@ import { preAllocatePThreadWorkerPool, instantiateWasmPThreadWorkerPool } from " import { export_linker } from "./exports-linker"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; import { getMemorySnapshot, storeMemorySnapshot, getMemorySnapshotSize } from "./snapshot"; -import { loadBootConfig } from "./blazor/_Integration"; +import { loadBootConfig, loadConfigFilesToVfs } from "./blazor/_Integration"; // legacy import { init_legacy_exports } from "./net6-legacy/corebindings"; @@ -380,7 +380,10 @@ async function mono_wasm_pre_init_full(): Promise { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_pre_init_full"); Module.addRunDependency("mono_wasm_pre_init_full"); - await mono_download_assets(); + await Promise.all([ + mono_download_assets(), + loadConfigFilesToVfs(INTERNAL.resourceLoader, config) + ]); Module.removeRunDependency("mono_wasm_pre_init_full"); } From 921f1573497aff435616cc064e0eff6029261e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 3 May 2023 17:56:50 +0200 Subject: [PATCH 2/9] Split loading and installing to VFS --- .../wasm/runtime/blazor/WebAssemblyConfigLoader.ts | 2 +- src/mono/wasm/runtime/blazor/_Integration.ts | 13 +++++++++---- src/mono/wasm/runtime/startup.ts | 6 ++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/mono/wasm/runtime/blazor/WebAssemblyConfigLoader.ts b/src/mono/wasm/runtime/blazor/WebAssemblyConfigLoader.ts index 1a98522bb6962e..decec9450ef005 100644 --- a/src/mono/wasm/runtime/blazor/WebAssemblyConfigLoader.ts +++ b/src/mono/wasm/runtime/blazor/WebAssemblyConfigLoader.ts @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { WebAssemblyStartOptions } from "../types-api"; import { BootJsonData } from "./BootConfig"; -import { WebAssemblyStartOptions } from "./WebAssemblyStartOptions"; export type ConfigFile = { name: string, diff --git a/src/mono/wasm/runtime/blazor/_Integration.ts b/src/mono/wasm/runtime/blazor/_Integration.ts index 3443ff9653c261..e1ceb6182bc122 100644 --- a/src/mono/wasm/runtime/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/blazor/_Integration.ts @@ -2,7 +2,7 @@ import { INTERNAL, Module } from "../globals"; import { MonoConfigInternal } from "../types"; import { AssetEntry, LoadingResource, WebAssemblyBootResourceType } from "../types-api"; import { BootConfigResult, BootJsonData, ICUDataMode } from "./BootConfig"; -import { WebAssemblyConfigLoader } from "./WebAssemblyConfigLoader"; +import { ConfigFile, WebAssemblyConfigLoader } from "./WebAssemblyConfigLoader"; import { WebAssemblyResourceLoader } from "./WebAssemblyResourceLoader"; import { hasDebuggingEnabled } from "./_Polyfill"; @@ -21,18 +21,23 @@ export async function loadBootConfig(config: MonoConfigInternal,) { Module.config = newConfig; } -export async function loadConfigFilesToVfs(resourceLoader: WebAssemblyResourceLoader | null, config: MonoConfigInternal) { +let configFiles: ConfigFile[] = []; + +export async function loadConfigFiles(resourceLoader: WebAssemblyResourceLoader | null, config: MonoConfigInternal) { if (!resourceLoader) { return; } - const configFiles = await WebAssemblyConfigLoader.initAsync(resourceLoader.bootConfig, config.applicationEnvironment!, config.startupOptions ?? {}); + configFiles = await WebAssemblyConfigLoader.initAsync(resourceLoader.bootConfig, config.applicationEnvironment!, config.startupOptions ?? {}); +} + +export function installConfigFilesToVfs() { for (let i = 0; i < configFiles.length; i++) { const configFile = configFiles[i]; Module.FS_createDataFile( "/", // TODO: Maybe working directory? configFile.name, - configFile.content, true /* canRead */, true /* canWrite */, true /* canOwn */ + new Uint8Array(configFile.content.buffer), true /* canRead */, true /* canWrite */, true /* canOwn */ ); } } diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 973fb6ef28a876..1b657ab88532f2 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -29,7 +29,7 @@ import { preAllocatePThreadWorkerPool, instantiateWasmPThreadWorkerPool } from " import { export_linker } from "./exports-linker"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; import { getMemorySnapshot, storeMemorySnapshot, getMemorySnapshotSize } from "./snapshot"; -import { loadBootConfig, loadConfigFilesToVfs } from "./blazor/_Integration"; +import { installConfigFilesToVfs, loadBootConfig, loadConfigFiles } from "./blazor/_Integration"; // legacy import { init_legacy_exports } from "./net6-legacy/corebindings"; @@ -258,6 +258,8 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { // signal this stage, this will allow pending assets to allocate memory beforeOnRuntimeInitialized.promise_control.resolve(); + installConfigFilesToVfs(); + await wait_for_all_assets(); // Diagnostics early are not supported with memory snapshot. See below how we enable them later. @@ -410,7 +412,7 @@ async function mono_wasm_pre_init_full(): Promise { await Promise.all([ mono_download_assets(), - loadConfigFilesToVfs(INTERNAL.resourceLoader, config) + loadConfigFiles(INTERNAL.resourceLoader, config) ]); Module.removeRunDependency("mono_wasm_pre_init_full"); From 46456eee77569bcbc0624e34247bb0c468d43423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 4 May 2023 12:56:57 +0200 Subject: [PATCH 3/9] Use asset collection to install appsettings to VFS --- src/mono/wasm/runtime/blazor/BootConfig.ts | 2 +- .../runtime/blazor/WebAssemblyConfigLoader.ts | 42 ------------ src/mono/wasm/runtime/blazor/_Integration.ts | 65 ++++++++++++------- src/mono/wasm/runtime/startup.ts | 9 +-- 4 files changed, 43 insertions(+), 75 deletions(-) delete mode 100644 src/mono/wasm/runtime/blazor/WebAssemblyConfigLoader.ts diff --git a/src/mono/wasm/runtime/blazor/BootConfig.ts b/src/mono/wasm/runtime/blazor/BootConfig.ts index 48c033b7368b38..bb37023602f80c 100644 --- a/src/mono/wasm/runtime/blazor/BootConfig.ts +++ b/src/mono/wasm/runtime/blazor/BootConfig.ts @@ -4,7 +4,7 @@ import { Module } from "../globals"; import { WebAssemblyBootResourceType } from "../types-api"; -type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; +export type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; export class BootConfigResult { private constructor(public bootConfig: BootJsonData, public applicationEnvironment: string) { diff --git a/src/mono/wasm/runtime/blazor/WebAssemblyConfigLoader.ts b/src/mono/wasm/runtime/blazor/WebAssemblyConfigLoader.ts deleted file mode 100644 index decec9450ef005..00000000000000 --- a/src/mono/wasm/runtime/blazor/WebAssemblyConfigLoader.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { WebAssemblyStartOptions } from "../types-api"; -import { BootJsonData } from "./BootConfig"; - -export type ConfigFile = { - name: string, - content: Uint8Array -} - -export class WebAssemblyConfigLoader { - static async initAsync(bootConfig: BootJsonData, applicationEnvironment: string, startOptions: Partial): Promise> { - const configFiles = await Promise.all((bootConfig.config || []) - .filter(name => name === "appsettings.json" || name === `appsettings.${applicationEnvironment}.json`) - .map(async name => ({ name, content: await getConfigBytes(name) }))); - - async function getConfigBytes(file: string): Promise { - // Allow developers to override how the config is loaded - if (startOptions.loadBootResource) { - const customLoadResult = startOptions.loadBootResource("configuration", file, file, ""); - if (customLoadResult instanceof Promise) { - // They are supplying an entire custom response, so just use that - return new Uint8Array(await (await customLoadResult).arrayBuffer()); - } else if (typeof customLoadResult === "string") { - // They are supplying a custom URL, so use that with the default fetch behavior - file = customLoadResult; - } - } - - const response = await fetch(file, { - method: "GET", - credentials: "include", - cache: "no-cache", - }); - - return new Uint8Array(await response.arrayBuffer()); - } - - return configFiles; - } -} diff --git a/src/mono/wasm/runtime/blazor/_Integration.ts b/src/mono/wasm/runtime/blazor/_Integration.ts index e1ceb6182bc122..426e72146bd327 100644 --- a/src/mono/wasm/runtime/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/blazor/_Integration.ts @@ -1,8 +1,7 @@ import { INTERNAL, Module } from "../globals"; import { MonoConfigInternal } from "../types"; import { AssetEntry, LoadingResource, WebAssemblyBootResourceType } from "../types-api"; -import { BootConfigResult, BootJsonData, ICUDataMode } from "./BootConfig"; -import { ConfigFile, WebAssemblyConfigLoader } from "./WebAssemblyConfigLoader"; +import { BootConfigResult, BootJsonData, ICUDataMode, LoadBootResourceCallback } from "./BootConfig"; import { WebAssemblyResourceLoader } from "./WebAssemblyResourceLoader"; import { hasDebuggingEnabled } from "./_Polyfill"; @@ -17,35 +16,14 @@ export async function loadBootConfig(config: MonoConfigInternal,) { INTERNAL.resourceLoader = resourceLoader; - const newConfig = mapBootConfigToMonoConfig(Module.config as MonoConfigInternal, resourceLoader, bootConfigResult.applicationEnvironment); + const newConfig = mapBootConfigToMonoConfig(Module.config as MonoConfigInternal, resourceLoader, bootConfigResult.applicationEnvironment, candidateOptions.loadBootResource); Module.config = newConfig; } -let configFiles: ConfigFile[] = []; - -export async function loadConfigFiles(resourceLoader: WebAssemblyResourceLoader | null, config: MonoConfigInternal) { - if (!resourceLoader) { - return; - } - - configFiles = await WebAssemblyConfigLoader.initAsync(resourceLoader.bootConfig, config.applicationEnvironment!, config.startupOptions ?? {}); -} - -export function installConfigFilesToVfs() { - for (let i = 0; i < configFiles.length; i++) { - const configFile = configFiles[i]; - Module.FS_createDataFile( - "/", // TODO: Maybe working directory? - configFile.name, - new Uint8Array(configFile.content.buffer), true /* canRead */, true /* canWrite */, true /* canOwn */ - ); - } -} - let resourcesLoaded = 0; let totalResources = 0; -export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, resourceLoader: WebAssemblyResourceLoader, applicationEnvironment: string): MonoConfigInternal { +export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, resourceLoader: WebAssemblyResourceLoader, applicationEnvironment: string, customLoadBootResource?: LoadBootResourceCallback): MonoConfigInternal { const resources = resourceLoader.bootConfig.resources; const assets: AssetEntry[] = []; @@ -175,6 +153,43 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, reso }; assets.push(asset); } + for (let i = 0; i < resourceLoader.bootConfig.config.length; i++) { + let config = resourceLoader.bootConfig.config[i]; + if (config === "appsettings.json" || config === `appsettings.${applicationEnvironment}.json`) { + const asset: AssetEntry = { + name: config, + resolvedUrl: `_framework/${name}`, + behavior: "vfs", + }; + + let response: Promise | null = null; + if (customLoadBootResource) { + const customLoadResult = customLoadBootResource("configuration", config, config, ""); + if (customLoadResult instanceof Promise) { + // They are supplying an entire custom response, so just use that + response = customLoadResult; + } else if (typeof customLoadResult === "string") { + // They are supplying a custom URL, so use that with the default fetch behavior + config = customLoadResult; + } + } + + if (!response) { + response = fetch(config, { + method: "GET", + credentials: "include", + cache: "no-cache", + }); + } + + asset.pendingDownload = { + name: config, + url: config, + response + }; + assets.push(asset); + } + } if (!hasIcuData) { moduleConfig.globalizationMode = "invariant"; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 1b657ab88532f2..a6d20cd699a857 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -29,7 +29,7 @@ import { preAllocatePThreadWorkerPool, instantiateWasmPThreadWorkerPool } from " import { export_linker } from "./exports-linker"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; import { getMemorySnapshot, storeMemorySnapshot, getMemorySnapshotSize } from "./snapshot"; -import { installConfigFilesToVfs, loadBootConfig, loadConfigFiles } from "./blazor/_Integration"; +import { loadBootConfig } from "./blazor/_Integration"; // legacy import { init_legacy_exports } from "./net6-legacy/corebindings"; @@ -258,8 +258,6 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { // signal this stage, this will allow pending assets to allocate memory beforeOnRuntimeInitialized.promise_control.resolve(); - installConfigFilesToVfs(); - await wait_for_all_assets(); // Diagnostics early are not supported with memory snapshot. See below how we enable them later. @@ -410,10 +408,7 @@ async function mono_wasm_pre_init_full(): Promise { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_pre_init_full"); Module.addRunDependency("mono_wasm_pre_init_full"); - await Promise.all([ - mono_download_assets(), - loadConfigFiles(INTERNAL.resourceLoader, config) - ]); + await mono_download_assets(); Module.removeRunDependency("mono_wasm_pre_init_full"); } From 6fc4ea1198b8aa1121c165f27dd8ee189824a6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 15 May 2023 10:47:02 +0200 Subject: [PATCH 4/9] Fix merge --- src/mono/wasm/runtime/loader/blazor/_Integration.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 17025e7a474060..432448a4f17953 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -22,7 +22,9 @@ export async function loadBootConfig(config: MonoConfigInternal, module: DotnetM } export async function initializeBootConfig(bootConfigResult: BootConfigResult, module: DotnetModuleInternal, startupOptions?: Partial) { - INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, startupOptions || {}); + const candidateOptions = startupOptions ?? {}; + INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, candidateOptions); + mapBootConfigToMonoConfig(loaderHelpers.config, bootConfigResult.applicationEnvironment, candidateOptions.loadBootResource); setupModuleForBlazor(module); } From f7edef4ab0a53cd70fdb70b335686a941f992a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 15 May 2023 11:41:31 +0200 Subject: [PATCH 5/9] Use WebAssemblyResourceLoader to load appsettings --- .../blazor/WebAssemblyResourceLoader.ts | 10 ++- .../runtime/loader/blazor/_Integration.ts | 61 +++++-------------- 2 files changed, 24 insertions(+), 47 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts index 8af4dba109e0d6..89a85b6edcb6cc 100644 --- a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts +++ b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts @@ -127,10 +127,16 @@ export class WebAssemblyResourceLoader { // Note that if cacheBootResources was explicitly disabled, we also bypass hash checking // This is to give developers an easy opt-out from the entire caching/validation flow if // there's anything they don't like about it. - return fetch(url, { + const fetchOptions: RequestInit = { cache: networkFetchCacheMode, integrity: this.bootConfig.cacheBootResources ? contentHash : undefined, - }); + }; + + if (resourceType === "configuration") { + fetchOptions.credentials = "include"; + } + + return fetch(url, fetchOptions); } private async addToCacheAsync(cache: Cache, name: string, cacheKey: string, response: Response) { diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 432448a4f17953..cfb34fe53fa50c 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -6,7 +6,7 @@ import type { AssetBehaviours, AssetEntry, LoadingResource, WebAssemblyBootResou import type { BootJsonData } from "../../types/blazor"; import { INTERNAL, loaderHelpers } from "../globals"; -import { BootConfigResult, LoadBootResourceCallback } from "./BootConfig"; +import { BootConfigResult } from "./BootConfig"; import { WebAssemblyResourceLoader } from "./WebAssemblyResourceLoader"; import { hasDebuggingEnabled } from "./_Polyfill"; import { ICUDataMode } from "../../types/blazor"; @@ -22,9 +22,8 @@ export async function loadBootConfig(config: MonoConfigInternal, module: DotnetM } export async function initializeBootConfig(bootConfigResult: BootConfigResult, module: DotnetModuleInternal, startupOptions?: Partial) { - const candidateOptions = startupOptions ?? {}; - INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, candidateOptions); - mapBootConfigToMonoConfig(loaderHelpers.config, bootConfigResult.applicationEnvironment, candidateOptions.loadBootResource); + INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, startupOptions ?? {}); + mapBootConfigToMonoConfig(loaderHelpers.config, bootConfigResult.applicationEnvironment); setupModuleForBlazor(module); } @@ -32,21 +31,20 @@ let resourcesLoaded = 0; let totalResources = 0; const behaviorByName = (name: string): AssetBehaviours | "other" => { - return name === "dotnet.timezones.blat" ? "vfs" - : name === "dotnet.native.wasm" ? "dotnetwasm" - : (name.startsWith("dotnet.native.worker") && name.endsWith(".js")) ? "js-module-threads" - : (name.startsWith("dotnet.native") && name.endsWith(".js")) ? "js-module-native" - : (name.startsWith("dotnet.runtime") && name.endsWith(".js")) ? "js-module-runtime" - : (name.startsWith("dotnet") && name.endsWith(".js")) ? "js-module-dotnet" - : name.startsWith("icudt") ? "icu" - : "other"; + return name === "dotnet.native.wasm" ? "dotnetwasm" + : (name.startsWith("dotnet.native.worker") && name.endsWith(".js")) ? "js-module-threads" + : (name.startsWith("dotnet.native") && name.endsWith(".js")) ? "js-module-native" + : (name.startsWith("dotnet.runtime") && name.endsWith(".js")) ? "js-module-runtime" + : (name.startsWith("dotnet") && name.endsWith(".js")) ? "js-module-dotnet" + : name.startsWith("icudt") ? "icu" + : "other"; }; const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | undefined } = { "assembly": "assembly", "pdb": "pdb", "icu": "globalization", - "vfs": "globalization", + "vfs": "configuration", "dotnetwasm": "dotnetwasm", }; @@ -78,7 +76,7 @@ export function setupModuleForBlazor(module: DotnetModuleInternal) { module.disableDotnet6Compatibility = false; } -export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, applicationEnvironment: string, customLoadBootResource?: LoadBootResourceCallback) { +export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, applicationEnvironment: string) { const resources = resourceLoader.bootConfig.resources; const assets: AssetEntry[] = []; @@ -163,40 +161,13 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl assets.push(asset); } for (let i = 0; i < resourceLoader.bootConfig.config.length; i++) { - let config = resourceLoader.bootConfig.config[i]; + const config = resourceLoader.bootConfig.config[i]; if (config === "appsettings.json" || config === `appsettings.${applicationEnvironment}.json`) { - const asset: AssetEntry = { + assets.push({ name: config, - resolvedUrl: `_framework/${name}`, + resolvedUrl: config, behavior: "vfs", - }; - - let response: Promise | null = null; - if (customLoadBootResource) { - const customLoadResult = customLoadBootResource("configuration", config, config, ""); - if (customLoadResult instanceof Promise) { - // They are supplying an entire custom response, so just use that - response = customLoadResult; - } else if (typeof customLoadResult === "string") { - // They are supplying a custom URL, so use that with the default fetch behavior - config = customLoadResult; - } - } - - if (!response) { - response = fetch(config, { - method: "GET", - credentials: "include", - cache: "no-cache", - }); - } - - asset.pendingDownload = { - name: config, - url: config, - response - }; - assets.push(asset); + }); } } From 241b483cb5fafc92747f21c6c57c5db4927c402e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 15 May 2023 12:12:19 +0200 Subject: [PATCH 6/9] Don't apply integrity on config files --- .../wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts index 89a85b6edcb6cc..87009c043ee5d8 100644 --- a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts +++ b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts @@ -128,12 +128,13 @@ export class WebAssemblyResourceLoader { // This is to give developers an easy opt-out from the entire caching/validation flow if // there's anything they don't like about it. const fetchOptions: RequestInit = { - cache: networkFetchCacheMode, - integrity: this.bootConfig.cacheBootResources ? contentHash : undefined, + cache: networkFetchCacheMode }; if (resourceType === "configuration") { fetchOptions.credentials = "include"; + } else { + fetchOptions.integrity = this.bootConfig.cacheBootResources ? contentHash : undefined; } return fetch(url, fetchOptions); From 24d6a3cbde48aba00204a8de4ae32112bd8e88ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 15 May 2023 12:20:09 +0200 Subject: [PATCH 7/9] Skip appsettings from resource cache --- .../wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts index 87009c043ee5d8..d133fca77ded9e 100644 --- a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts +++ b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts @@ -6,6 +6,8 @@ import type { BootJsonData, ResourceList } from "../../types/blazor"; import { toAbsoluteUri } from "./_Polyfill"; const networkFetchCacheMode = "no-cache"; +const cacheSkipResourceTypes = ["configuration"]; + export class WebAssemblyResourceLoader { private usedCacheKeys: { [key: string]: boolean } = {}; @@ -27,7 +29,7 @@ export class WebAssemblyResourceLoader { } loadResource(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): LoadingResource { - const response = this.cacheIfUsed + const response = this.cacheIfUsed && !cacheSkipResourceTypes.includes(resourceType) ? this.loadResourceWithCaching(this.cacheIfUsed, name, url, contentHash, resourceType) : this.loadResourceWithoutCaching(name, url, contentHash, resourceType); From 8345cb67018b26814b36dd145ddf898751dd65c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 15 May 2023 13:35:20 +0200 Subject: [PATCH 8/9] Comments --- .../wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts index d133fca77ded9e..d0960a287e96ab 100644 --- a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts +++ b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts @@ -134,8 +134,10 @@ export class WebAssemblyResourceLoader { }; if (resourceType === "configuration") { + // Include credentials so the server can allow download / provide user specific file fetchOptions.credentials = "include"; } else { + // Any other resource than configuration should provide integrity check fetchOptions.integrity = this.bootConfig.cacheBootResources ? contentHash : undefined; } From 5d79b604ca0c03f2764e6a1d6b3798a4fbf1dd74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 15 May 2023 14:07:18 +0200 Subject: [PATCH 9/9] WBT --- .../Blazor/AppsettingsTests.cs | 57 +++++++++++++++++++ .../wasm/Wasm.Build.Tests/BuildTestBase.cs | 13 +++-- 2 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs new file mode 100644 index 00000000000000..89ecb5b3c6a32a --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests.Blazor; + +public class AppsettingsTests : BuildTestBase +{ + public AppsettingsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + _enablePerTestCleanup = true; + } + + [Fact] + public async Task FileInVfs() + { + string id = $"blazor_{Path.GetRandomFileName()}"; + string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); + + string projectDirectory = Path.GetDirectoryName(projectFile)!; + + File.WriteAllText(Path.Combine(projectDirectory, "wwwroot", "appsettings.json"), $"{{ \"Id\": \"{id}\" }}"); + + string programPath = Path.Combine(projectDirectory, "Program.cs"); + string programContent = File.ReadAllText(programPath); + programContent = programContent.Replace("var builder", + """ + System.Console.WriteLine($"appSettings Exists '{File.Exists("/appsettings.json")}'"); + System.Console.WriteLine($"appSettings Content '{File.ReadAllText("/appsettings.json")}'"); + var builder + """); + File.WriteAllText(programPath, programContent); + + BlazorBuild(new BlazorBuildOptions(id, "debug", NativeFilesType.FromRuntimePack)); + + bool existsChecked = false; + bool contentChecked = false; + + await BlazorRunForBuildWithDotnetRun("debug", onConsoleMessage: msg => + { + if (msg.Text.Contains("appSettings Exists 'True'")) + existsChecked = true; + else if (msg.Text.Contains($"appSettings Content '{{ \"Id\": \"{id}\" }}'")) + contentChecked = true; + }); + + Assert.True(existsChecked, "File '/appsettings.json' wasn't found"); + Assert.True(contentChecked, "Content of '/appsettings.json' is not matched"); + } +} \ No newline at end of file diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index 17bc1d17ae79f9..8a0a8d3980d7f5 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -837,7 +837,7 @@ protected void AssertBlazorBundle(string config, bool isPublish, bool dotnetWasm same: dotnetWasmFromRuntimePack); } - protected void AssertBlazorBootJson(string config, bool isPublish, bool isNet7AndBelow, string targetFramework = DefaultTargetFrameworkForBlazor, string? binFrameworkDir=null) + protected void AssertBlazorBootJson(string config, bool isPublish, bool isNet7AndBelow, string targetFramework = DefaultTargetFrameworkForBlazor, string? binFrameworkDir = null) { binFrameworkDir ??= FindBlazorBinFrameworkDir(config, isPublish, targetFramework); @@ -932,17 +932,18 @@ public void BlazorAddRazorButton(string buttonText, string customCode, string me // Keeping these methods with explicit Build/Publish in the name // so in the test code it is evident which is being run! - public Task BlazorRunForBuildWithDotnetRun(string config, Func? test = null, string extraArgs = "--no-build") - => BlazorRunTest($"run -c {config} {extraArgs}", _projectDir!, test); + public Task BlazorRunForBuildWithDotnetRun(string config, Func? test = null, string extraArgs = "--no-build", Action? onConsoleMessage = null) + => BlazorRunTest($"run -c {config} {extraArgs}", _projectDir!, test, onConsoleMessage); - public Task BlazorRunForPublishWithWebServer(string config, Func? test = null, string extraArgs = "") + public Task BlazorRunForPublishWithWebServer(string config, Func? test = null, string extraArgs = "", Action? onConsoleMessage = null) => BlazorRunTest($"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files {extraArgs}", Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir(config, forPublish: true), "..")), - test); + test, onConsoleMessage); public async Task BlazorRunTest(string runArgs, string workingDirectory, Func? test = null, + Action? onConsoleMessage = null, bool detectRuntimeFailures = true) { using var runCommand = new RunCommand(s_buildEnv, _testOutput) @@ -968,6 +969,8 @@ void OnConsoleMessage(IConsoleMessage msg) Console.WriteLine($"[{msg.Type}] {msg.Text}"); _testOutput.WriteLine($"[{msg.Type}] {msg.Text}"); + onConsoleMessage?.Invoke(msg); + if (detectRuntimeFailures) { if (msg.Text.Contains("[MONO] * Assertion") || msg.Text.Contains("Error: [MONO] "))