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.
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. XamlDebuggingInformationis not set totrueautomatically.- 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-DEVIf 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:
| Configuration | Purpose | XamlDebuggingInformation |
|---|---|---|
| Debug-DEV | Local dev against DEV services | true |
| Debug-QUAL | Debugging against QUAL | true |
| Debug-PROD | Investigating a production issue | true |
| Release-DEV | Optimized build for DEV | false |
| Release-QUAL | Optimized build for QUAL | false |
| Release-PROD | The real thing | false |
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.propsis imported early, so a value set later in the.csprojwins. - 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.