Skip to content

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) #69341

Description

@borgez

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):

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"]
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:

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-blazorIncludes: Blazor, Razor Components

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions