diff --git a/.autover/changes/156dd8b1-1af2-45fc-b051-b94dc51fc56f.json b/.autover/changes/156dd8b1-1af2-45fc-b051-b94dc51fc56f.json new file mode 100644 index 000000000..0bb927899 --- /dev/null +++ b/.autover/changes/156dd8b1-1af2-45fc-b051-b94dc51fc56f.json @@ -0,0 +1,12 @@ +{ + "Projects": [ + { + "Name": "Amazon.Lambda.TestTool", + "Type": "Patch", + "ChangelogMessages": [ + "Fix Blazor web UI being non-interactive on .NET 9+/net10.0. Framework static assets (_framework/blazor.web.js) and the scoped-CSS bundle are now served via MapStaticAssets, the static web assets manifest is composed regardless of hosting environment, and the content root is pinned to the tool's install directory so the assets resolve correctly when running as an installed global tool.", + "Fix Razor class library content (e.g. the BlazorMonaco code-editor assets under _content/**) returning 404 on .NET 8 when running from a build output (dotnet run), which left the request/response editor uninitialized so selecting an example request could not populate the input. The static web assets manifest is now composed on all target frameworks, and .NET 8 additionally serves static files from the web root file provider so manifest-mapped _content/** assets resolve. Verified across .NET 8/.NET 10, Development/Production, and dotnet-run/installed-tool." + ] + } + ] +} diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Processes/TestToolProcess.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Processes/TestToolProcess.cs index 5da9d349b..6e3f6b19c 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Processes/TestToolProcess.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Processes/TestToolProcess.cs @@ -36,10 +36,24 @@ public class TestToolProcess /// public static TestToolProcess Startup(RunCommandSettings settings, CancellationToken cancellationToken = default) { - var builder = WebApplication.CreateBuilder(); + var builder = WebApplication.CreateBuilder(new WebApplicationOptions + { + // Pin the content root to the install dir. As a global tool the process launches from + // an arbitrary cwd, and WebRootFileProvider (= contentRoot/wwwroot) defaults to it. A + // foreign cwd points that provider at a nonexistent wwwroot, so assets 404 / serve empty. + ContentRootPath = AppContext.BaseDirectory + }); Utils.ConfigureWebApplicationBuilder(builder); + // Under `dotnet run`, the Blazor framework files (_framework/*) and RCL content + // (_content/BlazorMonaco/**) don't live in wwwroot — they're surfaced from the NuGet cache + // via the static-web-assets manifest. ASP.NET Core only composes that manifest into + // WebRootFileProvider automatically in Development, but this tool runs as Production, so we + // compose it explicitly here. Without it net9+ serves empty framework files (UI dead) and + // net8 404s the Monaco editor assets. No-op for the installed tool (everything's in wwwroot). + builder.WebHost.UseStaticWebAssets(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -91,15 +105,44 @@ public static TestToolProcess Startup(RunCommandSettings settings, CancellationT app.UseDeveloperExceptionPage(); } - // Always use the explicit file provider to serve static files from the tool's install - // directory. Without this, non-Production environments attempt to use the static web - // assets manifest which contains absolute paths from the build machine and will fail - // when running as an installed global tool on a different machine. + // --- Static assets: a base layer for every TFM, then per-TFM handling for manifest assets --- + + // Base layer (all TFMs, all deployments): classic wwwroot files (app.css, images, favicon). + // Authoritative for the installed tool, where every asset is published into wwwroot. Pinned to + // an explicit provider so it never depends on the manifest's absolute build-machine paths. app.UseStaticFiles(new StaticFileOptions { FileProvider = wwwrootFileProvider }); + // Manifest-mapped assets (framework files, scoped CSS, RCL _content/** e.g. Monaco) aren't in + // wwwroot under `dotnet run` — they're surfaced via the static-web-assets manifest that + // UseStaticWebAssets() composed above. How they're served differs by TFM: +#if NET9_0_OR_GREATER + // net9+: framework files + scoped CSS are served through the endpoint-routing static-assets + // API, not the classic middleware above. Without this, window.Blazor is never defined and the + // whole UI is non-interactive. MapStaticAssets() throws if the manifest (named after the entry + // assembly) is absent, as under a test host — so only map when it exists. + var staticAssetsManifest = Path.Combine( + AppContext.BaseDirectory, + $"{System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name}.staticwebassets.endpoints.json"); + if (File.Exists(staticAssetsManifest)) + { + app.MapStaticAssets(); + } +#else + // net8: no MapStaticAssets, and the base provider sees only the physical wwwroot. Serve the + // manifest-composed WebRootFileProvider too, or the RCL _content/** (Monaco) assets 404 under + // `dotnet run`. For the installed tool this resolves to the same wwwroot — a harmless second pass. + if (app.Environment.WebRootFileProvider is not null) + { + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = app.Environment.WebRootFileProvider + }); + } +#endif + app.UseAntiforgery(); app.MapRazorComponents()