Static web assets discovery only sees wwwroot content present at project evaluation time, not files written by a BeforeBuild/Build-hooked target
- Dominant language
- C#
- Stars
- 3.2k
- Forks
- 1.3k
- PR merge metrics
- PR metrics pending
Description
### Summary
A custom MSBuild target hooked at `BeforeTargets="BeforeBuild"` (or `"Build"`) that writes a generated file straight into `wwwroot` — the common pattern taught by several real "add Tailwind/Sass/esbuild to ASP.NET Core" tutorials — can silently fail to get a route in the published static web assets endpoints manifest (`{Project}.staticwebassets.endpoints.json`), even though the file itself is written to disk correctly at the right path and size. Under `MapStaticAssets()` (added in .NET 9), that means the request 404s in production despite the asset existing.
Originally filed against `dotnet/aspnetcore` — full investigation, multiple real repro repos, and CI runs are there: https://github.com/dotnet/aspnetcore/issues/68641. Filing here too since tracing it into the actual source landed in this repo, not aspnetcore.
### Root cause (as far as traced)
`ResolveProjectStaticWebAssets` is the target responsible for turning `wwwroot` content into `StaticWebAsset`/manifest entries:
https://github.com/dotnet/sdk/blob/7053edd97335c939e3d7692b4aea58e7c9c2bc6d/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets#L768-L812
```xml
...
`** inside the generating target — the pattern from Microsoft's own [Razor Class Library client assets guidance](https://devblogs.microsoft.com/dotnet/build-client-web-assets-for-your-razor-class-library/). This is the fix that's held up in every test.
### Minimal repro
https://github.com/Popl7/essessay-tailwind-manifest-repro — several branches isolating variables one at a time:
- `main` — original Tailwind CLI, `BeforeTargets="BeforeBuild"`
- `no-external-process-repro` — same bug with a trivial in-process `WriteLinesToFile`, no Tailwind, no external process at all
- `tutorial-pattern-repro` — the literal shape real tutorials teach (`BeforeTargets="Build"`)
- `dotnet8-repro` / `dotnet9-repro` / `dotnet11-repro` — same underlying discovery gap exists on .NET 8 too (confirmed via `obj/**/staticwebassets.build.json`), but is only user-visible from .NET 9 onward since `UseStaticFiles()` (pre-`MapStaticAssets()`) never consults that manifest for a plain app's own assets
- `official-pattern-repro` — the `Content`/`Link` fix, confirmed working
Each has a GitHub Actions workflow (`workflow_dispatch`) that builds with `--no-cache` and checks both the manifest and the actual HTTP response.
### What's still unexplained
The committed-vs-gitignored mechanism above is fully confirmed. What I haven't traced into the source: **from-scratch minimal repros with no pre-existing file on either side reproduce 100% reliably on GitHub Actions/Render, but never reproduce on local Docker Desktop (macOS, arm64 and amd64-emulated, under various CPU/memory constraints)** — same commit, same Dockerfile, same single `dotnet publish` invocation. Given `Content` is fixed at evaluation time either way, I'd naively expect this to be fully deterministic (always missing) once there's genuinely no pre-existing file — but it isn't, it's environment-dependent. Something after this point (possibly in Publish's own separate resolution pass, or an evaluation-cache detail) must be doing something I haven't found. Flagging in case it's a useful thread for someone closer to this code.
Happy to provide binlogs from a passing and a failing build if that would help narrow it down.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets, especially ResolveProjectStaticWebAssets and its CandidateAssets input. Run the minimal repro branches and their GitHub Actions workflows, comparing the generated static web assets manifest with the HTTP response. Use passing and failing binlogs to investigate the remaining environment-dependent behavior; done means the discovery behavior and expected manifest result are explained and covered by a regression test.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, github-actions
- Domain
- build-system, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100