"#if DEBUG in a .razor file is always false: dev panels leak into release"
▸ SYMPTOM
You wrap a debug-only panel (a dev overlay, a cheat menu, an inspector) in #if DEBUG inside a .razor component, expecting it to vanish from a published build. It doesn't. Either the block is gone even in the editor (so your dev tooling never appears), or the panel's source and styles turn up as readable files inside the shipped package. No compiler warning points at any of this.
▸ CAUSE
A preprocessor guard in razor markup cannot strip anything. Razor compiles to C# through a generator that parses the generated text with CSharpSyntaxTree.ParseText(text, null, path). That null is the CSharpParseOptions argument, which falls back to CSharpParseOptions.Default, and the default carries no preprocessor symbols at all, unlike a plain .cs file, which is parsed with the project's real symbol set.
So inside a .razor file:
#if DEBUGdeletes its block in every configuration (including the editor), becauseDEBUGis never defined.#if !DEBUGkeeps its block in every configuration, for the same reason.
Neither direction of the test does anything useful, and you get no warning either way.
Two more leaks stack on top of the dead guard, both independent of it:
- The razor source ships verbatim. The
.razorfile goes into the uploaded package's code archive as an extra file, before any stripping runs, so even a block correctly excluded from the compiled assembly still leaks as readable text in the shipped package. - A sibling
.scssuploads as a loose package file regardless. Extension-based publisher filters that reject.cs/.razortypically carry no rule for.scss, so the stylesheet ships even when the component doesn't.
▸ FIX
Build anything that must be genuinely absent from a release as a plain .cs class, not a .razor component.
A .cs file is parsed with the real symbol set and with StripDisabledTextTrivia, which deletes the disabled text before the archive is serialized. So a #if DEBUG-guarded .cs file is actually gone from a release build: assembly and archive both.
- Move the debug panel's logic (and markup, if you must, as C# UI construction) into a
.csclass guarded by#if DEBUG. - Put any stylesheet the guarded panel needs inside that same
#if DEBUGblock as a string constant, rather than as a separate.scssfile that would upload loose.
Prove the exclusion: don't assume it
Because the normal build gate compiles the Debug configuration, the excluded branch is never compiled by any routine check. To actually verify a release build drops it:
- Build the Release configuration explicitly.
- List the types in the output assembly and confirm the panel's types are absent.
- Byte-scan the assembly for the panel's marker strings in both UTF-8 and UTF-16: metadata names are UTF-8, string literals are UTF-16. A plain text search catches only half and can report a false pass.
▸ WHY IT WORKS
Conditional compilation only removes code the parser is told to disable. The razor generator never hands its parser your project's symbols, so its #if tests all evaluate against an empty symbol set; a .cs file gets the real symbols and the trivia-stripping pass, which is the combination that makes #if DEBUG mean what you expect. Keeping secrets or dev tooling out of a release is therefore a source-layout decision (what file type holds the code) as much as a #if decision.
- Published