Skip to content
Open
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
12 changes: 12 additions & 0 deletions .autover/changes/156dd8b1-1af2-45fc-b051-b94dc51fc56f.json
Original file line number Diff line number Diff line change
@@ -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."
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,24 @@ public class TestToolProcess
/// </summary>
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<IRuntimeApiDataStoreManager, RuntimeApiDataStoreManager>();
builder.Services.AddSingleton<IThemeService, ThemeService>();
builder.Services.AddSingleton<ILambdaClient, LambdaClient>();
Expand Down Expand Up @@ -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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the calls to UseStaticFiles and MapStaticAssets done below is the call still needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • UseStaticWebAssets() — tells the server where the framework/Monaco files actually are (the NuGet cache, via the manifest). Without it the server looks only in wwwroot, doesn't find them, and
    everything below has nothing to serve. This is the prerequisite for the other two.
  • UseStaticFiles(wwwroot) — serves the plain files that do live in wwwroot: app.css, images, favicon. Nothing else serves these, so without it the page loads unstyled.
  • MapStaticAssets() (net9+) — on .NET 9+ the framework files (blazor.web.js) and scoped CSS are served through this newer pipeline, not the classic one. Without it they come back empty,
    window.Blazor never loads, and the UI is dead. (.NET 8 has no MapStaticAssets, so a second UseStaticFiles over the manifest-composed provider does the same job.)

tried moving some of the code around and updating the comments to be easier to understand. but i tried testing without all of these options and some scenarios did not work

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legend for asset kinds:

  • classic = app.css, images, favicon (physically in wwwroot)
  • framework = _framework/blazor.web.js + scoped CSS (needed for interactivity)
  • RCL = _content/BlazorMonaco/** (needed for the code editor)
Framework Deployment classic served by framework served by RCL (Monaco) served by Extra thing that makes it work
net10 dotnet run UseStaticFiles(wwwroot) MapStaticAssets() MapStaticAssets() UseStaticWebAssets() — files are in the NuGet cache, not wwwroot
net10 installed tool UseStaticFiles(wwwroot) MapStaticAssets() MapStaticAssets() ContentRootPath pin — tool launches from an arbitrary cwd
net8 dotnet run UseStaticFiles(wwwroot) Blazor's own middleware 2nd UseStaticFiles(WebRoot) UseStaticWebAssets() — files are in the NuGet cache, not wwwroot
net8 installed tool UseStaticFiles(wwwroot) Blazor's own middleware UseStaticFiles(wwwroot) ContentRootPath pin — everything's in wwwroot, so the base call covers it

{
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<App>()
Expand Down
Loading