[browser] Load appsettings to VFS (#85520)
authorMarek Fišera <mara@neptuo.com>
Tue, 16 May 2023 07:34:13 +0000 (09:34 +0200)
committerGitHub <noreply@github.com>
Tue, 16 May 2023 07:34:13 +0000 (09:34 +0200)
src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs [new file with mode: 0644]
src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs
src/mono/wasm/runtime/loader/blazor/BootConfig.ts
src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts
src/mono/wasm/runtime/loader/blazor/_Integration.ts
src/mono/wasm/runtime/types/blazor.ts

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 (file)
index 0000000..89ecb5b
--- /dev/null
@@ -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
index 17bc1d17ae79f9b189a138613727565cef256345..8a0a8d3980d7f5f98efe0fa5ec40c624e45956fd 100644 (file)
@@ -837,7 +837,7 @@ namespace Wasm.Build.Tests
                         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 @@ namespace Wasm.Build.Tests
 
         // 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<IPage, Task>? test = null, string extraArgs = "--no-build")
-            => BlazorRunTest($"run -c {config} {extraArgs}", _projectDir!, test);
+        public Task BlazorRunForBuildWithDotnetRun(string config, Func<IPage, Task>? test = null, string extraArgs = "--no-build", Action<IConsoleMessage>? onConsoleMessage = null)
+            => BlazorRunTest($"run -c {config} {extraArgs}", _projectDir!, test, onConsoleMessage);
 
-        public Task BlazorRunForPublishWithWebServer(string config, Func<IPage, Task>? test = null, string extraArgs = "")
+        public Task BlazorRunForPublishWithWebServer(string config, Func<IPage, Task>? test = null, string extraArgs = "", Action<IConsoleMessage>? 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<IPage, Task>? test = null,
+                                        Action<IConsoleMessage>? onConsoleMessage = null,
                                         bool detectRuntimeFailures = true)
         {
             using var runCommand = new RunCommand(s_buildEnv, _testOutput)
@@ -968,6 +969,8 @@ namespace Wasm.Build.Tests
                     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] "))
index 9df0f5776937b101cb45bbc2de9e8fdb692829b3..2dd751cbf748e46a1c27db2b3b55f850a48a1781 100644 (file)
@@ -5,7 +5,7 @@ import type { BootJsonData } from "../../types/blazor";
 import type { WebAssemblyBootResourceType } from "../../types";
 import { loaderHelpers } from "../globals";
 
-type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise<Response> | null | undefined;
+export type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise<Response> | null | undefined;
 
 export class BootConfigResult {
     private constructor(public bootConfig: BootJsonData, public applicationEnvironment: string) {
index 8af4dba109e0d6a821009fb87b60de2972a58394..d0960a287e96ab30c9460e914f5574646ab0f6e1 100644 (file)
@@ -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);
 
@@ -127,10 +129,19 @@ 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, {
-            cache: networkFetchCacheMode,
-            integrity: this.bootConfig.cacheBootResources ? contentHash : undefined,
-        });
+        const fetchOptions: RequestInit = {
+            cache: networkFetchCacheMode
+        };
+
+        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;
+        }
+
+        return fetch(url, fetchOptions);
     }
 
     private async addToCacheAsync(cache: Cache, name: string, cacheKey: string, response: Response) {
index 27d99d92ea5cfcfa2031b1c2f08095e40c954f9c..22c0d65057836fcc916065fcd3f4f96124862cf5 100644 (file)
@@ -22,7 +22,7 @@ export async function loadBootConfig(config: MonoConfigInternal, module: DotnetM
 }
 
 export async function initializeBootConfig(bootConfigResult: BootConfigResult, module: DotnetModuleInternal, startupOptions?: Partial<WebAssemblyStartOptions>) {
-    INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, startupOptions || {});
+    INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, startupOptions ?? {});
     mapBootConfigToMonoConfig(loaderHelpers.config, bootConfigResult.applicationEnvironment);
     setupModuleForBlazor(module);
 }
@@ -31,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",
 };
 
@@ -161,6 +160,16 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl
         };
         assets.push(asset);
     }
+    for (let i = 0; i < resourceLoader.bootConfig.config.length; i++) {
+        const config = resourceLoader.bootConfig.config[i];
+        if (config === "appsettings.json" || config === `appsettings.${applicationEnvironment}.json`) {
+            assets.push({
+                name: config,
+                resolvedUrl: config,
+                behavior: "vfs",
+            });
+        }
+    }
 
     if (!hasIcuData) {
         moduleConfig.globalizationMode = "invariant";
index 95f88fd101775424bb6d19ef6bec8474dce50869..bf3ec8da02aa04f117673c1f10e3b8a9bf21d98c 100644 (file)
@@ -45,4 +45,4 @@ export enum ICUDataMode {
     All = 1,
     Invariant = 2,
     Custom = 3
-}
+}
\ No newline at end of file