-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
I'm not finding a ton of documentation out there, but I've been trying to wire up dotnet watch to look at a separate folder than wwwroot that would trigger a browser refresh, but I'm stuck.
It seems the StaticWebAssetPath metadata on MSBuild @(Watch) items is silently ignored by dotnet watch. This prevents users from enabling static file hot reload (browser refresh without rebuild) for custom file types outside of wwwroot/.
Repro Steps
- Create an ASP.NET Core 10 project
- Add a markdown file outside wwwroot (e.g., Content/docs/readme.md)
- Add the following to the .csproj:
<ItemGroup> <Watch Include="Content/**/*.md" StaticWebAssetPath="%(Identity)" /> </ItemGroup>
- Run dotnet watch
- Edit the markdown file
Expected Behavior
The file change should trigger a browser refresh via the UpdateStaticFile WebSocket message (same as files under wwwroot/), without triggering "No C# changes to apply."
Actual Behavior
dotnet watch ⌚ File updated: .\Content\docs\readme.md
dotnet watch ⌚ No C# changes to apply.
The file is watched, but goes through the managed code change handler instead of the static file handler.
My understand is that the routing decision happens in https://github.com/dotnet/sdk/blob/main/src/BuiltInTools/Watch/HotReload/StaticFileHandler.cs, lines 22-26:
if (file.StaticWebAssetPath is null)
{
allFilesHandled = false; // File passes through to managed code handler
continue; // Skip static file handling
}When StaticWebAssetPath is set:
- Line 28: Logs "Handling file change event for static content"
- Line 49: Adds file to browser refresh request list
- Line 64: Calls
UpdateStaticAssetsAsync()→ sends WebSocketUpdateStaticFilemessage to browser - Line 68: Logs "Hot reload of static assets succeeded"
When StaticWebAssetPath is null (current behavior):
- File is skipped (allFilesHandled = false)
- Falls through to
CompilationHandler.HandleManagedCodeChangesAsync() - Results in "No C# changes to apply."
Root Cause
In https://github.com/dotnet/sdk/blob/main/src/BuiltInTools/Watch/Build/EvaluationResult.cs, lines 120-127:
var items = projectInstance.GetItems(ItemNames.Compile)
.Concat(projectInstance.GetItems(ItemNames.AdditionalFiles))
.Concat(projectInstance.GetItems(ItemNames.Watch));
foreach (var item in items)
{
AddFile(item.EvaluatedInclude, staticWebAssetPath: null); // <-- BUG maybe?? Always null
}The StaticWebAssetPath metadata is always passed as null for @(Watch) items. Compare this to the @(Content) item handling (lines 129-144), which correctly reads and passes the metadata.
Meanwhile, the MSBuild targets file https://github.com/dotnet/sdk/blob/main/src/BuiltInTools/dotnet-watch/Watch/DotNetWatch.targets (lines 70-73) does set StaticWebAssetPath on @(Watch) items for content files:
<Watch Include="%(Content.FullPath)"
Condition="..."
StaticWebAssetPath="$(_DotNetWatchStaticWebAssetBasePath)$([System.String]::Copy('%(Identity)').Replace('\','/'))" />This metadata is populated but never read.
I haven't tested this yet, but it looks like a tweak might be in EvaluationResult.cs, read the StaticWebAssetPath metadata from @(Watch items:
var items = projectInstance.GetItems(ItemNames.Compile)
.Concat(projectInstance.GetItems(ItemNames.AdditionalFiles))
.Concat(projectInstance.GetItems(ItemNames.Watch));
foreach (var item in items)
{
var staticWebAssetPath = item.GetMetadataValue(MetadataNames.StaticWebAssetPath);
AddFile(item.EvaluatedInclude,
staticWebAssetPath: string.IsNullOrEmpty(staticWebAssetPath) ? null : staticWebAssetPath);
}This would allow users to:
- Enable static file hot reload for files outside wwwroot/
- Mark any file type (markdown, json, xml, etc.) as a static asset
- Use the existing
StaticWebAssetPathmetadata that's already documented in the targets file
I think this is only on .NET 10. I believe some enhancements were made that prevent refresh when there are no code changes, but the scenario where we are watching stuff that is not in wwwroot, c#, CSS or wwwroot was missed.
Environment
- .NET SDK version: 10.0.101
- OS: Windows