Fix WPF Hot Reload in Custom Configurations with XamlDebuggingInformation

XAML Hot Reload stopped working in your WPF project with a custom build configuration like Debug-DEV? Learn how the XamlDebuggingInformation csproj property fixes it, with copy-paste examples.

Share
Fix WPF Hot Reload in Custom Configurations with XamlDebuggingInformation
Photo by Vooglam Eyewear / Unsplash

The day half of my app stopped hot reloading

It started innocently. Our WPF application had grown into a multi-environment beast: a DEV server, a QUAL server, and PROD. For a while we juggled appsettings files and #if blocks, until someone proposed a cleaner idea: one build configuration per environment.

So we created Debug-DEV, Debug-QUAL, Debug-PROD, and their release twins Release-DEV, Release-QUAL, Release-PROD. Each configuration pointed to its own endpoints and feature flags through DefineConstants and config transforms. It felt great.

Then I noticed something strange. I was running Debug-DEV, editing XAML, and nothing happened. No live update, no refresh. But not everywhere: the main window hot reloaded perfectly, while a whole sub-portion of the solution (a UI library with shared views) was frozen. Same solution, same machine, same configuration name. Why would only one part of the app break?

After a long evening of checking Visual Studio settings and reinstalling workloads, I found the culprit hiding in plain sight in the .csproj: XamlDebuggingInformation.


What is XamlDebuggingInformation?

XamlDebuggingInformation is an MSBuild property used by the WPF build pipeline. When it is true, the XAML compiler embeds debugging information, such as line and position data, into the compiled output of your XAML files (BAML). The XAML tooling uses that information to map what is running in your app back to the source file you are editing. Without it, XAML Hot Reload has nothing to latch onto.

The important part is the default. WPF turns this option on for you, but only for the configuration literally named Debug. The condition is based on the exact configuration name, not on the fact that you consider your configuration “a debug one”.

So with a configuration named Debug-DEV:

  • The SDK does not recognize it as Debug.
  • XamlDebuggingInformation is not set to true automatically.
  • XAML Hot Reload silently stops working for that project.

There is no error, just a silent loss of the feature, which makes it so annoying to track down.


Why only a sub-portion of my project broke

This was the confusing part. My main application project had been created early and had explicit property groups for each configuration. The shared UI library had been added later, and its .csproj only contained the bare minimum. Because its configuration name wasn’t Debug, it never got the default XamlDebuggingInformation=true.

Hot reload only works for XAML compiled with that debugging information. The projects that had it worked, and the ones that didn’t stayed static. That is how one solution ended up with both working and dead hot reload.


The fix: set XamlDebuggingInformation explicitly

Add the property in a configuration-specific PropertyGroup:

<PropertyGroup Condition="'$(Configuration)' == 'Debug-DEV'">
  <XamlDebuggingInformation>true</XamlDebuggingInformation>
</PropertyGroup>

Then repeat it for each debug configuration, or, better, use a single condition for all of them:

<PropertyGroup Condition="$(Configuration.StartsWith('Debug'))">
  <XamlDebuggingInformation>true</XamlDebuggingInformation>
</PropertyGroup>

This covers Debug-DEV, Debug-QUAL, and Debug-PROD in one go.


Do it once for the whole solution with Directory.Build.props

If you have many projects, don’t copy and paste that block into every .csproj. Create a Directory.Build.props file at the root of your solution:

<Project>
  <!-- Enable XAML hot reload for every Debug-* configuration -->
  <PropertyGroup Condition="$(Configuration.StartsWith('Debug'))">
    <XamlDebuggingInformation>true</XamlDebuggingInformation>
    <DebugType>embedded</DebugType>
    <Optimize>false</Optimize>
  </PropertyGroup>

  <!-- Make sure Release-* never ships with XAML debug info -->
  <PropertyGroup Condition="$(Configuration.StartsWith('Release'))">
    <XamlDebuggingInformation>false</XamlDebuggingInformation>
    <Optimize>true</Optimize>
  </PropertyGroup>
</Project>

Now every project, including that forgotten UI library, behaves consistently. That is exactly what fixed my “half-working” solution.

A note on Optimize=false: hot reload also generally needs unoptimized code, so make sure your custom debug configurations are not optimized. If you copied your Release settings by accident, hot reload can break for that reason too.


How to verify the value MSBuild actually uses

Before blaming Visual Studio, ask MSBuild directly (SDK 8.0 or later):

dotnet msbuild MyApp.Wpf.csproj -getProperty:XamlDebuggingInformation -p:Configuration=Debug-DEV

If the output is empty or false, you have found your problem. Run it against each project in the solution to find the one that is out of line.


A naming convention that scales

Since the whole point was separating environments, here is the structure that worked well for us:

ConfigurationPurposeXamlDebuggingInformation
Debug-DEVLocal dev against DEV servicestrue
Debug-QUALDebugging against QUALtrue
Debug-PRODInvestigating a production issuetrue
Release-DEVOptimized build for DEVfalse
Release-QUALOptimized build for QUALfalse
Release-PRODThe real thingfalse

Using a consistent Debug-* / Release-* prefix is what makes the StartsWith condition so simple and safe.


Common pitfalls

  • The configuration exists in the solution but not in every project. Check the solution configuration manager so each project maps to the right configuration.
  • A project-level property overrides your global one. Directory.Build.props is imported early, so a value set later in the .csproj wins.
  • Hot Reload enabled in Visual Studio but not working. Check Tools > Options > Debugging > .NET/C++ Hot Reload as well as XAML Hot Reload, but only after you have confirmed the property value.
  • Forgetting to clean. After changing the property, rebuild fully so the XAML is recompiled with the debugging information.

Conclusion

What looked like a mysterious Visual Studio bug was actually a naming assumption. WPF gives you XamlDebuggingInformation=true for free, but only when your configuration is named exactly Debug. The moment you introduce environment-specific configurations like Debug-DEV, you take on the responsibility of setting it yourself.

The takeaway is simple: whenever you create custom build configurations in WPF, set XamlDebuggingInformation explicitly, ideally once in Directory.Build.props with a StartsWith('Debug') condition. You keep a clean separation between DEV, QUAL, and PROD, and you keep the instant feedback loop that makes XAML development enjoyable. And if hot reload ever works in only one part of your solution again, you will know exactly where to look first.