#69341·aspnetcore

Microsoft.AspNetCore.App.Internal.Assets is only injected when .razor files exist at restore time — the canonical Docker "restore before copying sources" pattern silently publishes a Blazor app without _framework assets (blazor.web.js → 404)

Author: borgezCreated Sep 16, 2026Updated Sep 17, 2026
Labelsarea-blazor

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Building a Blazor Web App with the canonical .NET Docker layering pattern

COPY <only *.csproj / *.props / global.json / NuGet.config> ./
RUN  dotnet restore
COPY . .
RUN  dotnet build --no-restore
RUN  dotnet publish --no-build

produces an image where the _framework static web assets are missing. The published app returns 404 for /_framework/blazor.web.js and for the fingerprinted URL the page actually references (/_framework/blazor.web.<hash>.js). Everything else (the app itself, app.css) works.

Nothing fails during the build: dotnet restore, dotnet build, dotnet publish and docker build all exit with 0, and the breakage only shows up at runtime in the browser.

Root cause. blazor.web.js / blazor.server.js are no longer embedded in the assembly; they are delivered by the implicit package Microsoft.AspNetCore.App.Internal.Assets (see #65061, and dotnet/sdk#46165 where making it conditional was intentional). That implicit PackageReference is only added when the project already contains Razor items at evaluation time — i.e. at dotnet restore. In the Docker pattern above the restore happens on a tree that has the .csproj but no .razor files, so the package never ends up in project.assets.json. dotnet build --no-restore then keeps that package graph, the static web assets pipeline never sees the framework assets, and the publish output + *.staticwebassets.endpoints.json are produced without _framework routes.

Steps To Reproduce

Minimal repro repository: https://github.com/borgez/blazor-internal-assets-docker-repro./verify.sh builds three images and prints the HTTP status codes:

Dockerfile               home=200  /_framework/blazor.web.js=404
Dockerfile.fixed         home=200  /_framework/blazor.web.js=200
Dockerfile.copy-first    home=200  /_framework/blazor.web.js=200

Repro repo (a hand-written equivalent of dotnet new blazor --interactivity Server, ~6 source files — Microsoft.NET.Sdk.Web, AddRazorComponents().AddInteractiveServerComponents(), app.UseStaticFiles(), app.MapStaticAssets(), app.MapRazorComponents<App>().AddInteractiveServerRenderMode(), and <script src="@Assets["_framework/blazor.web.js"]"></script> in App.razor):

dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY --parents **/*.csproj ./          # ← restore sees a tree without .razor
RUN dotnet restore "BlazorMin.csproj"
COPY . .
RUN dotnet build "BlazorMin.csproj" --no-restore -c $BUILD_CONFIGURATION

FROM build AS publish
RUN dotnet publish "BlazorMin.csproj" --no-restore --no-build -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "BlazorMin.dll"]
bash
docker build -t repro .
docker run -d -p 8080:8080 repro
curl -o /dev/null -w '%{http_code}\n' http://localhost:8080/                          # 200
curl -o /dev/null -w '%{http_code}\n' http://localhost:8080/_framework/blazor.web.js  # 404

Changing only the first COPY line fixes it:

dockerfile
COPY --parents **/*.csproj **/*.razor ./
# /_framework/blazor.web.js → 200, fingerprinted /_framework/blazor.web.f7fxoh55ae.js → 200

What exactly drives the implicit reference — restore-time project content, one variable at a time (dotnet restore on each tree, then inspect project.assets.json):

project tree at restore time Microsoft.AspNetCore.App.Internal.Assets in assets file
T.csproj only absent (0 libraries)
T.csproj + wwwroot/app.css absent
T.csproj + empty wwwroot/ absent
T.csproj + Components/App.razor present (10.0.10)

Expected Behavior

Any one of these would be acceptable:

  1. The implicit PackageReference does not depend on source files being present (copying only project files first is the documented Docker caching pattern), or
  2. dotnet build/dotnet publish fail loudly when the assets file no longer matches the package graph the project evaluates to (currently the mismatch is completely silent), or
  3. At minimum, a warning when a Microsoft.NET.Sdk.Web project with Razor content publishes without any _framework static web assets.

Additional context

Reproduced on a real application as well (Blazor Web App, 78 .razor files, MapStaticAssets()):

restore without sources **/*.razor copied before restore
project.assets.json Microsoft.AspNetCore.App.Internal.Assets missing …/10.0.10 present
/app/publish/wwwroot/_framework/ missing present (6 files, blazor.web.js ~200 KB)
*.staticwebassets.endpoints.json 52 723 B, no _framework/blazor.web.js route 66 766 B, route present
GET /_framework/blazor.web.js 404 200

Environment: mcr.microsoft.com/dotnet/sdk:10.0 = 10.0.302 (Microsoft.AspNetCore.App.Internal.Assets/10.0.10), runtime mcr.microsoft.com/dotnet/aspnet:10.0; also reproduced with local SDK 10.0.201; Docker 29.8 / buildx 0.37; net10.0.

Related: #65061, dotnet/sdk#46165, #64897, #64545, #65291.

Happy to attach a binlog if that helps.