Skip to content

Fix hotreload duplicate assets#52816

Merged
lewing merged 31 commits intodotnet:mainfrom
lewing:fix-hotreload-duplicate-assets
Feb 6, 2026
Merged

Fix hotreload duplicate assets#52816
lewing merged 31 commits intodotnet:mainfrom
lewing:fix-hotreload-duplicate-assets

Conversation

@lewing
Copy link
Member

@lewing lewing commented Feb 3, 2026

An extension of #52710

Summary

This PR fixes the duplicate key error in FilterStaticWebAssetEndpoints that occurs when multiple Blazor WASM client projects are hosted by a single server.

Root Cause

When multiple Blazor WASM clients (e.g., FirstClient and SecondClient) are referenced by a host server project, both clients define the same HotReload JS module (Microsoft.DotNet.HotReload.WebAssembly.Browser.lib.module.js) as a static web asset. Since both reference the same SDK file path, they have identical Identity values, causing a duplicate key error.

Fix

Following the established pattern used by JSModules, ScopedCss, and ServiceWorker targets:

  1. Copy to intermediate folder: The HotReload JS module is copied to $(IntermediateOutputPath)hotreload\ before being defined as a StaticWebAsset
  2. Unique Identity: Each project now has a unique Identity for the asset (pointing to their own intermediate folder)
  3. No filter changes needed: The copy-to-intermediate pattern naturally avoids duplicates without modifying filter logic

This follows @javiercn's guidance: "What we do in other situations with assets like this is to copy them to the intermediate output folder before defining them."

Testing

The Publish_HostingMultipleBlazorWebApps_Works test now passes.

cc @javiercn @maraf

maraf and others added 20 commits January 19, 2026 13:04
When multiple Blazor WASM client projects are hosted by a single server,
both clients define the same HotReload JS module as a static web asset.
This causes a duplicate key error in FilterStaticWebAssetEndpoints.

Root cause: The ShouldIncludeAssetAsReference filter logic did not exclude
CurrentProject assets when the source project uses ProjectMode=Root (which
is the case for standalone Blazor WASM apps).

Fix:
1. Mark the HotReload JS module with AssetMode=CurrentProject in Sdk.targets
2. Fix the filter logic to always exclude CurrentProject assets when
   providing assets as a reference, regardless of the source project's mode

Fixes the test: Publish_HostingMultipleBlazorWebApps_Works
@github-actions github-actions bot added the Area-AspNetCore RazorSDK, BlazorWebAssemblySDK, StaticWebAssetsSDK label Feb 3, 2026
@dotnet-policy-service
Copy link
Contributor

Thanks for your PR, @@lewing.
To learn about the PR process and branching schedule of this repo, please take a look at the SDK PR Guide.

@lewing lewing changed the base branch from main to maraf/WasmHotReloadEmbed February 3, 2026 20:46
@lewing lewing requested review from Copilot and javiercn February 3, 2026 20:46
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a duplicate key error in FilterStaticWebAssetEndpoints that occurs when multiple Blazor WASM client projects are hosted by a single server. The issue was caused by the HotReload JS module asset being incorrectly included as a reference asset when it should only be used by the current project.

Changes:

  • Added AssetMode="CurrentProject" to the HotReload JS module asset definition to mark it as current-project-only
  • Fixed the ShouldIncludeAssetAsReference method to always exclude CurrentProject assets when providing assets as references, regardless of project mode

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/WasmSdk/Sdk/Sdk.targets Added AssetMode="CurrentProject" attribute to the HotReload JS module asset to prevent it from flowing to consuming projects
src/StaticWebAssetsSdk/Tasks/Data/StaticAssetsManifest.cs Fixed the boolean logic in ShouldIncludeAssetAsReference to always exclude CurrentProject assets when providing assets as references

@lewing lewing changed the base branch from maraf/WasmHotReloadEmbed to main February 3, 2026 21:00
Removed unnecessary HelixCorrelationPayload entry for shipping packages.
@lewing
Copy link
Member Author

lewing commented Feb 5, 2026

@tmat please take a look

</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\BuiltInTools\HotReloadAgent.WebAssembly.Browser\Microsoft.DotNet.HotReload.WebAssembly.Browser.csproj">
Copy link
Member

Choose a reason for hiding this comment

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

Should this have <ReferenceOutputAssembly>false</ReferenceOutputAssembly>?

Copy link
Member

@tmat tmat Feb 5, 2026

Choose a reason for hiding this comment

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

You'd also want to avoid flowing the current project TFM to the target project like so:

<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<UndefineProperties>TargetFramework;TargetFrameworks</UndefineProperties>

If you also set

<OutputItemType>AdditionalContent</OutputItemType>
<Pack>true</Pack>
<PackagePath>hotreload</PackagePath>

you should be able to remove <AdditionalContent Include="$(ArtifactsBinDir)\Microsoft.DotNet.HotReload.WebAssembly.Browser\$(Configuration)\**\*.*"> below.

The ProjectReference should create this item.

See https://github.com/dotnet/sdk/blob/main/src/BuiltInTools/Watch/RuntimeDependencies.props#L5 for an example.

Copy link
Member

Choose a reason for hiding this comment

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

The build is failing because of this:

src\WasmSdk\Tasks\Microsoft.NET.Sdk.WebAssembly.Tasks.csproj(0,0): error NU1201: (NETCORE_ENGINEERING_TELEMETRY=Restore) Project Microsoft.DotNet.HotReload.WebAssembly.Browser is not compatible with net472 (.NETFramework,Version=v4.7.2).

…atibility

Address @tmat's review: add SkipGetTargetFrameworkProperties,
ReferenceOutputAssembly=false, UndefineProperties to avoid TFM
negotiation with the net472 target. Use OutputItemType=AdditionalContent
to replace the separate AdditionalContent glob, following the pattern
from RuntimeDependencies.props.
…et10.0

Two issues introduced by review feedback commits:

1. OutputItemType=AdditionalContent on ProjectReference doesn't flow
   PackagePath metadata to the created items (confirmed by @maraf in
   PR dotnet#52710). Restored the explicit AdditionalContent glob from
    that was working in the green commit.

2. HotReload csproj TFM changed from net10.0 to
   but Sdk.targets hardcodes _WasmHotReloadTfm=net10.0. Per @tmat's
   guidance in PR dotnet#52710: pin to net10.0 until higher version needed.

The ProjectReference still has SkipGetTargetFrameworkProperties,
ReferenceOutputAssembly=false, and UndefineProperties for net472
compatibility as @tmat requested.
@lewing
Copy link
Member Author

lewing commented Feb 6, 2026

Reverted the last two review-feedback commits (75d9e18, 8adbe75) because they broke the SDK layout in CI. Two issues:

1. OutputItemType=AdditionalContent doesn't flow PackagePath metadata.
The suggestion to replace the explicit glob with OutputItemType=AdditionalContent + PackagePath=hotreload on the ProjectReference sounded clean, but MSBuild doesn't copy PackagePath (or any custom metadata from the ProjectReference item) onto the output items it creates. @maraf already hit this in #52710 and confirmed PackagePath ends up empty. Since PrepareAdditionalFilesToLayout filters out items where PackagePath is blank, the hotreload files never made it into the SDK layout — they only landed in tools/, which Sdk.targets doesn't look at.

The RuntimeDependencies.props example actually uses a different pattern (OutputItemType=None + TargetPath + CopyToOutputDirectory), which doesn't go through this project's custom layout mechanism at all, so it wasn't directly applicable here.

Restored the explicit AdditionalContent glob, which is the same pattern used for the targets/ and Sdk/ directories in this same file.

2. TFM needs to stay pinned to net10.0.
Sdk.targets hardcodes _WasmHotReloadTfm=net10.0 and looks for files under hotreload/net10.0/. Switching the HotReload csproj to `` (currently net11.0) put the output under `hotreload/net11.0/` instead → `MSB3030: file not found`. Pinned back to `net10.0` per @tmat's earlier guidance in #52710.

Kept the ProjectReference attributes from review (SkipGetTargetFrameworkProperties, ReferenceOutputAssembly=false, UndefineProperties) since those correctly establish the build dependency without trying to resolve the assembly or its TFM.

@lewing lewing requested a review from tmat February 6, 2026 00:28
@lewing lewing merged commit 494e1e4 into dotnet:main Feb 6, 2026
25 checks passed
@lewing lewing deleted the fix-hotreload-duplicate-assets branch February 6, 2026 15:28
@lewing
Copy link
Member Author

lewing commented Feb 6, 2026

/backport to release/11.0-preview1

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Started backporting to release/11.0-preview1 (link to workflow run)

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

@lewing an error occurred while backporting to release/11.0-preview1. See the workflow output for details.

@lewing
Copy link
Member Author

lewing commented Feb 6, 2026

/backport to /release/11.0.1xx-preview1

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Started backporting to /release/11.0.1xx-preview1 (link to workflow run)

@lewing
Copy link
Member Author

lewing commented Feb 6, 2026

/backport to release/11.0.1xx-preview1

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Started backporting to release/11.0.1xx-preview1 (link to workflow run)

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

@lewing an error occurred while backporting to /release/11.0.1xx-preview1. See the workflow output for details.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

@lewing backporting to release/11.0.1xx-preview1 failed, the patch most likely resulted in conflicts. Please backport manually!

git am output
$ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch

Applying: Fix test assets
Applying: Remove dependency on Package for running tests
Applying: Embed HotReload in WasmSDK
Applying: Reference embedded HotReload
Applying: Define SWA for JSInitializer
Applying: Fix content root
Applying: Don't pack the package
Applying: More properties
Applying: Feedback
Applying: Copy JS part through project reference
Applying: Fix
Applying: ProjectReference with manual asset copy
Applying: Fix
Applying: Fix
Applying: Refactor
Applying: Baselines
Applying: Don't add .nuget as package source
Using index info to reconstruct a base tree...
M	build/RunTestsOnHelix.cmd
M	build/RunTestsOnHelix.sh
Falling back to patching base and 3-way merge...
Auto-merging build/RunTestsOnHelix.cmd
CONFLICT (content): Merge conflict in build/RunTestsOnHelix.cmd
Auto-merging build/RunTestsOnHelix.sh
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0017 Don't add .nuget as package source
Error: The process '/usr/bin/git' failed with exit code 128

Link to workflow output

lewing added a commit that referenced this pull request Feb 6, 2026
Co-authored-by: Marek Fišera <mara@neptuo.com>
lewing added a commit that referenced this pull request Feb 6, 2026
Co-authored-by: Marek Fišera <mara@neptuo.com>
@lewing lewing mentioned this pull request Feb 6, 2026
2 tasks
lewing added a commit that referenced this pull request Feb 6, 2026
The code was using:
  <_WasmHotReloadModule OriginalItemSpec="%(Identity)" />

This creates a new empty item instead of setting metadata on the existing item.

Fixed to use proper MSBuild Update syntax:
  <_WasmHotReloadModule Update="@(_WasmHotReloadModule)">
    <OriginalItemSpec>%(Identity)</OriginalItemSpec>
  </_WasmHotReloadModule>

This issue was introduced in #52816 and could cause DefineStaticWebAssets
to receive empty items, leading to potential build failures or incorrect
static web asset processing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-AspNetCore RazorSDK, BlazorWebAssemblySDK, StaticWebAssetsSDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants