dotnet watch

Use dotnet watch for hot reload while editing an API, know which edits need a restart, and control the set of files it watches.

Signature
dotnet watch [run|test|build] [--project <path>] [--no-hot-reload] [--non-interactive] [--list] [-q|-v] [-- <app args>]
  • Added in .NET .NET Core 1.0 (bundled with the SDK since .NET Core 2.1)

Parameters

ParameterTypeDescription
$commandoptionalrun | test | build= runThe dotnet command to run and re-run.
$--projectoptionalpath= the project in the current folderProject to watch.
$--no-hot-reloadoptionalflag= falseRestart the process on every change instead of patching the running app.
$--non-interactiveoptionalflag= falseNever prompt; on an edit hot reload cannot apply, restart automatically.
$--listoptionalflag= falsePrint the files that would be watched and exit.
$-q, -voptionalflagQuieter or more verbose watcher output.

Return value

process exit codeRuns until stopped with Ctrl+C; returns the exit code of the watched command when it exits.

Errors

Prints compile errors and waits for the next save instead of exiting. Edits that hot reload cannot apply (rude edits) trigger a prompt or an automatic restart.

Changelog

  • PHP .NET 6Hot reload: supported edits are applied to the running process without a restart.

dotnet watch wraps another command (run by default) and re-runs it when files change. For run it also does hot reload: it starts the app with DOTNET_MODIFIABLE_ASSEMBLIES=debug, compiles each saved change to a delta and applies it to the running process through the runtime's metadata update API. Requests in flight keep running; the next call to a changed method uses the new code.

What hot reload can and cannot do

EditResult
Change a method body, a lambda handler, a LINQ queryApplied in place
Add a method, property or new typeUsually applied
Change top-level Program.cs (register a service, add middleware, map a route)Applied, but the code already ran at start-up: no effect until restart
Change a method signature, a generic constraint, a record's positional parametersRude edit: needs a restart
Edit .csproj or add a packageFull rebuild and restart

The confusing case is the middle row: the edit "worked", no error shows, yet the new endpoint returns 404. Press Ctrl+R to restart. For rude edits the watcher asks whether to restart; --non-interactive restarts without asking, which suits a terminal you do not watch.

Caches that outlive the edit

Hot reload changes code, not state. A singleton that cached computed prices, a compiled regex built from a constant, or a memoised route table still hold old results. An assembly-level [MetadataUpdateHandler(typeof(T))] names a type whose static ClearCache(Type[]?) and UpdateApplication(Type[]?) methods run after each update, so you can clear them.

What is watched

By default the project's Compile and EmbeddedResource items plus referenced projects, and appsettings*.json through configuration reload. Add <Watch Include="Sql/**/*.sql" /> for other inputs, and mark items or project references Watch="false" to ignore them. dotnet watch --list prints the final set. In Docker bind mounts or network drives file events often never arrive; set DOTNET_USE_POLLING_FILE_WATCHER=1.

Rules for the team

  • Development only. The watched app runs Debug, under the launch profile, with modifiable assemblies; never use it in a container that serves real traffic.
  • The app sees DOTNET_WATCH=1; use it to skip slow start-up work, never to change behaviour that tests should cover.
  • dotnet watch test re-runs tests on save; pair it with a filter so it stays fast.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Iterate on an order-pricing handler with hot reload and see the change on the next request without losing the running process
  • Clear a memoised discount table on every hot reload with a MetadataUpdateHandler
  • Run dotnet watch inside a Docker dev container where file events only arrive with polling enabled

Frequently asked questions

I added an endpoint and dotnet watch said the change was applied, but it returns 404. Why?

Route mapping runs once at start-up. Hot reload patched Program.cs but did not re-run it. Restart with Ctrl+R; endpoint, middleware and DI changes always need a restart.

Why does dotnet watch never notice my changes in Docker?

File system events from a bind mount often do not reach the container. Set DOTNET_USE_POLLING_FILE_WATCHER=1.

Can I use hot reload in production?

No. It needs a Debug build with modifiable assemblies and the SDK's watcher. Deploy changes through the normal publish pipeline.

More examples

Controlling the watched files

<code>Watch</code> items add inputs and <code>Watch="false"</code> removes them. Excluding generated files stops a code generator and the watcher from triggering each other in a loop; <code>dotnet watch --list</code> confirms the result.

Example · xml
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <!-- SQL scripts embedded and read at start-up: restart when they change. -->
    <EmbeddedResource Include="Sql/**/*.sql" />
    <Watch Include="Sql/**/*.sql" />

    <!-- Generated client code is rewritten by a tool; do not react to it. -->
    <Compile Update="Generated/**/*.cs" Watch="false" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="../Orders.Contracts/Orders.Contracts.csproj" Watch="false" />
  </ItemGroup>
</Project>

Watch in a dev container

Bind mounts from macOS and Windows hosts often do not deliver file change events into the container, so the watcher never fires; polling fixes it at some CPU cost. The port is published on loopback only, so the developer exception page and OpenAPI document are not reachable from the local network. <code>--non-interactive</code> matters because nobody can answer the rude-edit prompt in a container log.

Example · yaml
services:
  orders-api:
    image: mcr.microsoft.com/dotnet/sdk:10.0
    working_dir: /src
    command: ["dotnet", "watch", "--project", "src/Orders.Api", "--non-interactive", "--", "--urls", "http://0.0.0.0:8080"]
    environment:
      DOTNET_USE_POLLING_FILE_WATCHER: "1"
      ASPNETCORE_ENVIRONMENT: Development
    ports:
      - "127.0.0.1:5080:8080"
    volumes:
      - ./:/src

Everyday watch commands

<code>--no-hot-reload</code> is the escape hatch when patched state confuses you: each save becomes a clean restart, which is slower but always truthful. Watching tests with a filter keeps the feedback loop to seconds.

Example · bash
# API with hot reload, launch profile applied
dotnet watch --project src/Orders.Api

# Restart on every change: for code that hot reload handles badly
dotnet watch --project src/Orders.Api --no-hot-reload

# Re-run fast tests on save
dotnet watch test --project tests/Orders.UnitTests --filter "Category=Unit"

# What is being watched?
dotnet watch --project src/Orders.Api --list

Discussion

  • Be the first to comment on this lesson.