Summary
Add support for the x:Code XAML directive, allowing developers to define C# members (methods, fields, properties) directly inside XAML files without a separate code-behind file.
This becomes a natural complement to XAML C# Expressions (XEXPR). Where XEXPR enables inline expressions in attribute values ({= expr}, lambdas, etc.), x:Code enables inline member declarations — event handlers, helper methods, backing fields — completing the "single-file component" story.
Motivation
With XEXPR, developers can already write:
<Button Text="Add" Clicked="{(s, e) => Items.Add(new Item())}" />
But anything beyond a one-liner forces a code-behind file. x:Code closes the gap:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.MainPage">
<x:Code><![CDATA[
ObservableCollection<Item> Items { get; } = new();
void OnRefresh(object sender, EventArgs e)
{
Items.Clear();
foreach (var item in DataService.Load())
Items.Add(item);
}
]]></x:Code>
<VerticalStackLayout>
<Button Text="Refresh" Clicked="{OnRefresh}" />
<CollectionView ItemsSource="{Items}" />
</VerticalStackLayout>
</ContentPage>
Scenarios unlocked
| Scenario |
Today |
With x:Code |
| Simple event handler |
Requires .xaml.cs file |
Inline in XAML |
| Bindable helper property |
Requires .xaml.cs file |
Inline in XAML |
| Self-contained sample/snippet |
Two files minimum |
Single .xaml file |
| DataTemplates with per-template logic |
External static helpers |
Colocated code |
Hot Reload
Hot Reload is supported through the existing C# Hot Reload infrastructure. Since x:Code blocks are emitted as regular members of the partial class by the source generator, edits to x:Code content are standard C# member changes from the compiler's perspective and flow through the C# HR pipeline.
Constraints (same as WPF + MAUI-specific)
WPF-inherited constraints
These match the WPF x:Code specification:
x:Code must be an immediate child element of the root element.
x:Class must be provided on the root element.
- Code is emitted into the scope of the existing partial class — all declarations become members/variables of that class.
- Cannot define additional top-level classes (nesting is allowed but atypical).
- Cannot define or add CLR namespaces beyond the partial class namespace — external types must be fully qualified (or use
using aliases inside the block if the language supports it).
- Conflicts with code-behind members are compiler errors — standard partial class rules apply.
MAUI-specific constraints
- Requires XAML Source Generator (XSG) —
x:Code is not supported by runtime XAML inflation or XamlC IL rewriting. Only the source generator pipeline can emit the code block into the generated partial class.
- Requires
<EnablePreviewFeatures>true</EnablePreviewFeatures> — same gating as XEXPR (diagnostic MAUIX2012 when missing).
- Content should be wrapped in
<![CDATA[...]]> — recommended to avoid XML-escaping <, >, & in C# code. The parser already handles CDATA nodes.
- Multiple
x:Code blocks are allowed — each is concatenated into the partial class body (order-preserved). This keeps code colocated with the UI section it supports.
Interaction with XEXPR
x:Code and XEXPR are complementary:
- XEXPR → inline expressions in attribute values (bindings, event lambdas, computed values)
- x:Code → member declarations (methods, properties, fields)
Members declared in x:Code are accessible from XEXPR expressions via this. (or bare identifier if unambiguous), following existing XEXPR resolution rules (MAUIX2007/2008 diagnostics for ambiguity).
Diagnostics
| Code |
Condition |
MAUIX2012 |
EnablePreviewFeatures not set (reuse existing) |
| New |
x:Code not an immediate child of root element |
| New |
x:Code used without x:Class on root |
| New |
x:Code used with runtime or XamlC inflator (XSG required) |
Open questions
1. x:Code in XAML without x:Class (ResourceDictionary)
Today the source generator already compiles XAML files without an explicit x:Class (primarily ResourceDictionary files) by synthesizing a temporary class. Should x:Code be supported in that scenario?
Arguments for: A ResourceDictionary with value converters or helper methods defined inline via x:Code would be fully self-contained — no satellite .cs file needed.
Arguments against: The synthesized class is an implementation detail with no stable identity; members declared in x:Code would exist on a type that consumers can't reference. This may create confusion about member visibility and testability.
Tentative answer: Defer — require x:Class initially (constraint #2). Revisit if there's demand for self-contained RD files.
2. Follow-up: make .xaml.cs code-behind optional
With x:Code, the code-behind file becomes redundant for simple pages. As a follow-up, the source generator should emit a complete code-behind (including constructor and InitializeComponent() call) when no .xaml.cs file is present — making true single-file XAML components possible:
<!-- No .xaml.cs file needed -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.SimplePage">
<x:Code><![CDATA[
void OnTap(object sender, EventArgs e) => Title = "Tapped!";
]]></x:Code>
<Button Text="Tap me" Clicked="{OnTap}" />
</ContentPage>
This is a separate work item that depends on x:Code landing first.
3. IDE integration (code completion, diagnostics)
x:Code blocks contain C# code embedded inside XAML (typically inside <![CDATA[...]]>). The source generator will emit this code verbatim into a partial class, so compiler diagnostics will surface — but with mapped locations pointing into the XAML file, which IDEs may or may not handle gracefully.
Key questions:
- Code completion: Can VS / VS Code provide IntelliSense inside
x:Code CDATA blocks? This likely requires IDE-side awareness of the embedded language context (similar to how Razor handles @code {} blocks). Without it, developers get correctness (compiler errors) but lose discoverability.
- Syntax highlighting: XAML editors currently treat CDATA as opaque text. C# highlighting inside
x:Code would require editor extensions or language server cooperation.
- Error mapping: The source generator can emit
#line directives to map errors back to XAML line numbers. This works for compiler errors but may not integrate with IDE inline error display inside CDATA.
Tentative answer: Ship x:Code without IDE-specific tooling initially — compiler errors and HR work out of the box. IDE richness (completion, highlighting) is a tooling follow-up that can be pursued independently, potentially by the VS XAML Language Service team.
Non-goals
x:Code in non-root elements (e.g., inside DataTemplates) — out of scope for initial implementation.
- Multi-language support — C# only, matching XEXPR.
- Runtime interpretation — compilation only, no eval/scripting.
Summary
Add support for the
x:CodeXAML directive, allowing developers to define C# members (methods, fields, properties) directly inside XAML files without a separate code-behind file.This becomes a natural complement to XAML C# Expressions (XEXPR). Where XEXPR enables inline expressions in attribute values (
{= expr}, lambdas, etc.),x:Codeenables inline member declarations — event handlers, helper methods, backing fields — completing the "single-file component" story.Motivation
With XEXPR, developers can already write:
But anything beyond a one-liner forces a code-behind file.
x:Codecloses the gap:Scenarios unlocked
.xaml.csfile.xaml.csfile.xamlfileHot Reload
Hot Reload is supported through the existing C# Hot Reload infrastructure. Since
x:Codeblocks are emitted as regular members of the partial class by the source generator, edits tox:Codecontent are standard C# member changes from the compiler's perspective and flow through the C# HR pipeline.Constraints (same as WPF + MAUI-specific)
WPF-inherited constraints
These match the WPF
x:Codespecification:x:Codemust be an immediate child element of the root element.x:Classmust be provided on the root element.usingaliases inside the block if the language supports it).MAUI-specific constraints
x:Codeis not supported by runtime XAML inflation or XamlC IL rewriting. Only the source generator pipeline can emit the code block into the generated partial class.<EnablePreviewFeatures>true</EnablePreviewFeatures>— same gating as XEXPR (diagnosticMAUIX2012when missing).<![CDATA[...]]>— recommended to avoid XML-escaping<,>,&in C# code. The parser already handles CDATA nodes.x:Codeblocks are allowed — each is concatenated into the partial class body (order-preserved). This keeps code colocated with the UI section it supports.Interaction with XEXPR
x:Codeand XEXPR are complementary:Members declared in
x:Codeare accessible from XEXPR expressions viathis.(or bare identifier if unambiguous), following existing XEXPR resolution rules (MAUIX2007/2008 diagnostics for ambiguity).Diagnostics
MAUIX2012EnablePreviewFeaturesnot set (reuse existing)x:Codenot an immediate child of root elementx:Codeused withoutx:Classon rootx:Codeused with runtime or XamlC inflator (XSG required)Open questions
1.
x:Codein XAML withoutx:Class(ResourceDictionary)Today the source generator already compiles XAML files without an explicit
x:Class(primarilyResourceDictionaryfiles) by synthesizing a temporary class. Shouldx:Codebe supported in that scenario?Arguments for: A ResourceDictionary with value converters or helper methods defined inline via
x:Codewould be fully self-contained — no satellite.csfile needed.Arguments against: The synthesized class is an implementation detail with no stable identity; members declared in
x:Codewould exist on a type that consumers can't reference. This may create confusion about member visibility and testability.Tentative answer: Defer — require
x:Classinitially (constraint #2). Revisit if there's demand for self-contained RD files.2. Follow-up: make
.xaml.cscode-behind optionalWith
x:Code, the code-behind file becomes redundant for simple pages. As a follow-up, the source generator should emit a complete code-behind (including constructor andInitializeComponent()call) when no.xaml.csfile is present — making true single-file XAML components possible:This is a separate work item that depends on
x:Codelanding first.3. IDE integration (code completion, diagnostics)
x:Codeblocks contain C# code embedded inside XAML (typically inside<![CDATA[...]]>). The source generator will emit this code verbatim into a partial class, so compiler diagnostics will surface — but with mapped locations pointing into the XAML file, which IDEs may or may not handle gracefully.Key questions:
x:CodeCDATA blocks? This likely requires IDE-side awareness of the embedded language context (similar to how Razor handles@code {}blocks). Without it, developers get correctness (compiler errors) but lose discoverability.x:Codewould require editor extensions or language server cooperation.#linedirectives to map errors back to XAML line numbers. This works for compiler errors but may not integrate with IDE inline error display inside CDATA.Tentative answer: Ship
x:Codewithout IDE-specific tooling initially — compiler errors and HR work out of the box. IDE richness (completion, highlighting) is a tooling follow-up that can be pursued independently, potentially by the VS XAML Language Service team.Non-goals
x:Codein non-root elements (e.g., inside DataTemplates) — out of scope for initial implementation.