Skip to content

Add Binding converter culture support#35821

Open
jfversluis wants to merge 4 commits into
net11.0from
jfversluis/binding-converter-culture
Open

Add Binding converter culture support#35821
jfversluis wants to merge 4 commits into
net11.0from
jfversluis/binding-converter-culture

Conversation

@jfversluis

@jfversluis jfversluis commented Jun 9, 2026

Copy link
Copy Markdown
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description

Adds Binding.ConverterCulture support for #5696 so bindings can pass an explicit CultureInfo to IValueConverter.Convert and ConvertBack instead of always using CultureInfo.CurrentUICulture.

The implementation preserves existing Binding constructor signatures and wires the property through runtime bindings, typed bindings, runtime XAML, XamlC, and source-generated XAML.

This intentionally does not add new ConverterCulture APIs to MultiBinding or TemplateBinding. MultiBinding parity can be considered separately, and TemplateBinding has no ConverterCulture property to propagate.

Issues Fixed

Fixes #5696

Testing

  • git diff --check
  • dotnet build src/Controls/src/Build.Tasks/Controls.Build.Tasks.csproj --no-restore
  • dotnet build src/Controls/src/SourceGen/Controls.SourceGen.csproj --no-restore
  • dotnet test src/Controls/tests/BindingSourceGen.UnitTests/Controls.BindingSourceGen.UnitTests.csproj --no-restore
  • dotnet test src/Controls/tests/SourceGen.UnitTests/SourceGen.UnitTests.csproj --no-restore
  • LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 dotnet test src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj --no-restore
  • LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 dotnet test src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj --no-restore -p:WarningsNotAsErrors=XC0618%3BXC0619%3BXC0022%3BXC0023%3BXC0025%3BXC0045%3BXC0067%3BMAUIG2045%3BMAUID1000%3BMAUIX2005%3BMAUIX2015%3BMAUIX2017

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35821

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35821"

@github-actions github-actions Bot added the area-xaml XAML, CSS, Triggers, Behaviors label Jun 9, 2026
@kubaflo

kubaflo commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

AI code review refresh for net11.0 target

Head reviewed: 755e88460be8df7394148f32819e740ace0fbe03
Target: net11.0 · State: Open (Draft)
Verdict: 🟡 Needs discussion (implementation is solid; one parity question + CI is red for unrelated reasons)

Summary

Independent read of the diff before the narrative. Binding.ConverterCulture is wired consistently through every binding path:

  • Runtime Binding / TypedBindingBase (GetSourceValue/GetTargetValue now pass ConverterCulture instead of hardcoded CultureInfo.CurrentUICulture)
  • Runtime XAML (BindingExtension), XamlC IL (SetPropertiesVisitor), source‑gen compiled (CompiledBindingMarkup) and reflection markup (KnownMarkups)
  • PublicAPI.Unshipped.txt updated across all TFMs for both Core and Xaml assemblies.

The default getter _converterCulture ?? CultureInfo.CurrentUICulture preserves prior behavior, and Clone() copies the raw field (not the resolved value), so an unset culture stays dynamic. This is correct and explicitly covered by tests.

What's good

  • Backwards compatible: existing constructors untouched; default behavior unchanged when ConverterCulture is not set.
  • Strong test coverage: BindingConverterCultureTests exercises runtime + typed bindings, explicit override, and clone semantics (both explicit-preserved and unset-stays-dynamic). Maui5696.xaml runs across all inflators (Runtime/XamlC/SourceGen), matching repo conventions (internal void + [XamlInflatorData] + nested [Collection("Issue")]).

Findings (non-blocking)

  1. [Medium] MultiBinding has no parity. MultiBinding still hardcodes CultureInfo.CurrentUICulture (src/Controls/src/Core/MultiBinding.cs:211,230). If MultiBinding is intentionally out of scope for Add ConverterCulture property on Binding to allow overriding of culture settings #5696, a one-line note in the PR description would avoid confusion; otherwise consider a follow-up.
  2. [Low] Template bindings excluded. CompiledBindingMarkup gates with !isTemplateBinding && _node.HasProperty("ConverterCulture"), so ConverterCulture is silently dropped for template bindings. Confirm this is intended (TemplateBinding has no such property) so it isn't a surprise.
  3. [Low] Null setter resets to dynamic. Setting ConverterCulture = null reverts to CurrentUICulture; PublicAPI ~ (nullable-oblivious) entries are consistent with the surrounding file. Fine, just noting the semantics.

CI note

maui-pr is red, but every failure I inspected is unrelated to this change:

  • Build macOS (Debug) — infra error MSB4019: …/.buildtasks/Microsoft.Maui.Core.props was not found (build-tasks not staged), not a source/compile error from this PR.
  • Windows Helix Unit Tests (Release) — only Microsoft.Maui.Essentials.AI.UnitTests.dll failed (9/10 work items passed); Essentials AI is unrelated to Controls/binding. The Controls/XAML unit-test work items passed, which is positive signal that the new tests are green.
  • RunOniOS MauiRelease / TrimFull / AOT — pre-existing IL2026 trim-analysis errors in HybridWebViewHandler (System.Text.Json), → NETSDK1144. Unrelated to binding culture.
  • Build Analysis red reflects the above.

No PR-caused CI failure was identified. I did not exhaustively open every job, so attribution is high-confidence but not absolute.

Confidence

Code correctness: medium‑high. CI attribution: medium‑high.


⚠️ Non-approval disclaimer: This is an automated AI review refresh for the net11.0 target. It is not an approval and does not constitute a --approve/--request-changes review. A human maintainer must make the merge decision. The PR is currently a draft.

@jfversluis

jfversluis commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

@StephaneDelcroix would you mind taking a look at this one when you have a chance? This targets net11.0 and adds Binding.ConverterCulture support for #5696.

@jfversluis jfversluis marked this pull request as ready for review June 12, 2026 15:15
@kubaflo

kubaflo commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

AI code review refresh for net11.0 target

Head reviewed: 0a97f917588d6e37c7f8c5ecc51eb75d612897e7
Target: net11.0 · State: Open
Verdict: 🟢 LGTM — the prior round's parity gap is now closed; CI red is entirely unrelated/pre-existing.

What changed since the last refresh (755e884)

  • 922c304Trigger CI (no source change)
  • 0a97f91Add MultiBinding converter culture support

This directly resolves the [Medium] finding from the round‑14 review (MultiBinding previously hardcoded CultureInfo.CurrentUICulture).

Independent diff read

ConverterCulture is now wired consistently across every binding surface:

  • Runtime Binding, TypedBindingBase, and now MultiBindingGetSourceValue/GetTargetValue/ConvertBack pass ConverterCulture instead of the hardcoded CultureInfo.CurrentUICulture.
  • XAML paths: BindingExtension, XamlC IL (SetPropertiesVisitor), source‑gen compiled (CompiledBindingMarkup), reflection markup (KnownMarkups).
  • PublicAPI.Unshipped.txt updated across all TFMs for both Core and Xaml assemblies (Binding, MultiBinding, TypedBindingBase getters/setters).

The default getter _converterCulture ?? CultureInfo.CurrentUICulture preserves legacy behavior, and MultiBinding.Clone() copies the raw field (ConverterCulture = _converterCulture) — matching the Binding/TypedBinding pattern, so an unset culture stays dynamic. Correct and consistent.

Prior review reconciliation (round 14)

  1. [Medium] MultiBinding parity → ✅ Resolved in 0a97f91. MultiBinding.ConverterCulture added with [TypeConverter(typeof(CultureInfoConverter))], ThrowIfApplied() guard, clone preservation, and pass-through in both Convert/ConvertBack. New tests cover convert, convert-back, explicit-preserved clone, and dynamic-default clone.
  2. [Low] Template bindings excluded → unchanged; still gated by !isTemplateBinding && _node.HasProperty("ConverterCulture") in CompiledBindingMarkup. Intentional (TemplateBinding has no ConverterCulture property).
  3. [Low] ConverterCulture = null reverts to dynamic → unchanged; PublicAPI ~ (nullable‑oblivious) entries remain consistent. Noted, not blocking.

Findings this round

None new. The previously noted low‑severity items are intentional design choices, not defects.

CI status (build 1461702)

maui-pr and Build Analysis are red, but every failure I inspected is unrelated to this change:

  • Windows Helix Unit Tests (Debug) — only failed work item is Microsoft.Maui.Essentials.AI.UnitTests.dll; all four Controls/XAML unit-test runs report 0 failed (4652/4685, 4650/4685, 4643/4678 ×2 — remainder skipped). Strong positive signal that the new binding-culture tests are green.
  • RunOniOS MauiReleaseTrimFull / TrimFull_CoreCLR & AOT macOS — pre-existing IL2026 trim-analysis errors against System.Text.Json/HybridWebViewNETSDK1144 (124× IL2026 in the trim log). Same failure class as round 14; not introduced here.
  • Build Analysis red reflects the above.

The round‑14 macOS Debug build infra error (MSB4019) is gone this round — that build now passes.

Blast radius

Binding infrastructure is high‑fanout, but the change is additive and opt‑in: behavior only differs when ConverterCulture is explicitly set; otherwise the resolved value is the unchanged CultureInfo.CurrentUICulture. Clone semantics preserve the dynamic-default. No threading concern introduced (still reads a per-binding field/ambient culture at convert time).

Confidence

Code correctness: high. CI attribution: medium‑high (I did not exhaustively open every job, but failed work items were inspected directly).


⚠️ Non-approval disclaimer: This is an automated AI review refresh for the net11.0 target. It is not an approval and does not constitute a --approve/--request-changes review. A human maintainer must make the merge decision.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #35821 — Add Binding ConverterCulture support (issue #5696)

Verdict: NEEDS_DISCUSSION (confidence: medium) — HEAD 0a97f91. The code is well-structured — 3 of 4 models (gpt-5.5, opus-4.6, gemini) return LGTM with only minor suggestions (opus-4.8 timed out without a verdict). The hold is a PR-specific CI failure, below.

Implementation

Adds a ConverterCulture to Binding/MultiBinding/TypedBinding, the XAML BindingExtension, and threads it through both the XamlC (SetPropertiesVisitor) and source-generated (CompiledBindingMarkup/KnownMarkups) binding paths, with PublicAPI.Unshipped.txt entries across all TFMs. Unit tests (BindingConverterCultureTests, MultiBindingTests) and a XAML test (Maui5696 using ConverterCulture='nl-NL') are included.

⚠️ Blocking question — Windows Helix unit-test (Debug) leg is red only on this PR

maui-pr → Run Helix Unit Tests Windows (Debug) is failing on this head (buildId 1461702), while the Release variant passes. I checked the same Debug leg on 4 other concurrently-open PRs (#35597, #35265, #34136, #35922) — it is green on all of them, so this is not the usual flake; it is specific to this PR, which adds the new binding-culture tests.

Likely root cause to check: whether the ConverterCulture string (e.g. 'nl-NL' in Maui5696.xaml) is converted to a CultureInfo (via CultureInfoConverter) consistently across all three binding paths — runtime reflection, XamlC (SetPropertiesVisitor), and SourceGen (KnownMarkups). If one path leaves it as a raw string or applies a different default (CurrentCulture vs InvariantCulture), a culture assertion would fail. Please confirm which test fails on the Windows Debug leg and fix, or confirm it's an unrelated flake via rerun.

Minor suggestions (inline, non-blocking)

  • 💡 KnownMarkups.cs:455 — generated code allocates a new CultureInfoConverter per binding; consider caching.
  • 💡 SetPropertiesVisitor.cs:387 — consider ConverterCulture parity for TemplateBindingExtension.

The other red legs (Build Analysis, AOT macOS, RunOniOS_MauiRelease/TrimFull/CoreCLR) are the known unrelated flakes.

if (converterCultureNode is ValueNode { Value: string converterCulture })
{
expression += $"ConverterCulture = (global::System.Globalization.CultureInfo)new global::System.ComponentModel.CultureInfoConverter().ConvertFromInvariantString({SymbolDisplay.FormatLiteral(converterCulture, true)})!, ";
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The source-generated binding code instantiates a fresh CultureInfoConverter for each binding when a ConverterCulture string is present. Since CultureInfoConverter is stateless, consider emitting a single cached/static converter (or converting the culture string once at generation time into a CultureInfo.GetCultureInfo("...") call) to avoid a per-binding allocation on a hot path. Minor — not blocking.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion — addressed in e21a5d0. Literal converter culture values now emit CultureInfo.GetCultureInfo("..."), so generated code no longer allocates a CultureInfoConverter per binding.


if (bindingExtensionType.Value.Item3 == "BindingExtension")
{
// extension.TypedBinding.ConverterCulture = extension.ConverterCulture;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For full parity, consider whether ConverterCulture should also be supported on TemplateBindingExtension, which shares much of this binding-construction path. Right now ConverterCulture is wired for Binding/MultiBinding but a TemplateBinding with a converter won't honor a culture. Non-blocking — flagging for completeness/consistency.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this out intentionally for this PR. TemplateBindingExtension currently has no ConverterCulture API surface, so adding parity there would require a separate public API addition beyond the Binding/MultiBinding support in scope here.

@jfversluis

Copy link
Copy Markdown
Member Author

Addressed the source-gen allocation review comment in e21a5d0 by emitting CultureInfo.GetCultureInfo(...) for literal ConverterCulture strings. I also checked the Windows Debug Helix failure from build 1461702: the failed work item is Microsoft.Maui.Essentials.AI.UnitTests.dll, specifically StreamingJsonDeserializerTests.FileBasedTests.ProcessChunk_TxtFile_DeserializesProgressively(... maui-itinerary-5.txt ...), so it is unrelated to the binding/XAML tests in this PR.

@kubaflo

kubaflo commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

✅ LGTM — no blocking issues found

Re-review PR #35821 — Binding ConverterCulture support (new head e21a5d0)

Verdict: LGTM (confidence: high) — the previously-blocking issue is resolved. 🎉

What changed

The one-line fix in KnownMarkups.cs replaces
ConverterCulture = (CultureInfo)new CultureInfoConverter().ConvertFromInvariantString(...)
with
ConverterCulture = CultureInfo.GetCultureInfo(...).

This does two good things at once:

  1. Fixes the PR-specific Windows Helix unit-test failure I flagged last round — the Run Helix Unit Tests Windows (Debug) and (Release) legs now pass on this head (build 1466073). The old CultureInfoConverter().ConvertFromInvariantString(...) path was the culprit; CultureInfo.GetCultureInfo(...) is the canonical, well-defined parse.
  2. Addresses my earlier suggestion about the per-binding CultureInfoConverter allocation — GetCultureInfo is cached internally, so there's no per-binding converter allocation on the source-generated path anymore.

Assessment

The original review already had the code at 3/4 models LGTM; the only reason it was held at NEEDS_DISCUSSION was that PR-specific Windows test failure, which is now fixed. The ConverterCulture wiring across the runtime / XamlC / SourceGen paths is consistent, and the PublicAPI entries are present across all TFMs.

Non-blocking (optional, from last round)

  • 💡 Consider ConverterCulture parity for TemplateBindingExtension (it shares the binding-construction path). Purely a completeness nice-to-have — not required for this PR.

CI

The Windows build + Helix unit-test legs are green/pending-green; the remaining red legs (AOT macOS, RunOniOS_MauiRelease/TrimFull/CoreCLR) are the known unrelated infra flakes. Ready for human review/merge once the full maui-pr build (currently pending) completes green.

Multi-model review (gpt-5.5 · opus-4.8 · opus-4.6 · gemini-3.1-pro). Comments only — not a formal approval.

jfversluis and others added 4 commits June 17, 2026 11:18
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jfversluis jfversluis force-pushed the jfversluis/binding-converter-culture branch from e21a5d0 to 21eeedc Compare June 17, 2026 09:23
@kubaflo

kubaflo commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

✅ LGTM — no blocking issues found

Re-review PR #35821 — Add Binding ConverterCulture support (issue #5696)

Verdict: LGTM (confidence: high) — HEAD 21eeedca. Unanimous: all 4 models (gpt-5.5, opus-4.8, opus-4.6, gemini-3.1-pro) approved with 0 findings, and independent HEAD validation confirms it.

What changed

The PR adds Binding.ConverterCulture so a binding can pass an explicit CultureInfo to IValueConverter.Convert/ConvertBack instead of always using CultureInfo.CurrentUICulture (483 additions, 25 files). The property is wired consistently across runtime Binding/TypedBinding/MultiBinding, runtime XAML, XamlC IL, and source-generated XAML, with full PublicAPI.Unshipped.txt entries across every TFM. The new HEAD commit 21eeedca ("Optimize generated converter culture literals") is a single-line source-gen change in KnownMarkups.cs, replacing a per-binding new CultureInfoConverter().ConvertFromInvariantString(...) with the internally-cached CultureInfo.GetCultureInfo(...). This content is identical to the previously-approved head e21a5d01; the branch was simply rebased onto net11.0 (which explains gemini's "rebase" framing vs. the other models' "code change" framing — both describe the same end state).

Validation

I confirmed the three correctness-critical points against HEAD code rather than trusting the model summaries: (1) the getter resolves an unset value dynamically — get { return _converterCulture ?? CultureInfo.CurrentUICulture; } (Binding.cs:91); (2) Clone() copies the raw backing field — ConverterCulture = _converterCulture (Binding.cs:214) — correctly preserving the unset (dynamic CurrentUICulture) vs. explicit distinction; and (3) the source-gen GetCultureInfo literal is strictly better than the old converter allocation and semantically equivalent for the realistic culture-name inputs XAML produces (e.g. en-US). The earlier round's only actionable suggestion (the source-gen converter allocation) is now resolved by this very commit; MultiBinding parity was added in aed0df46; and TemplateBinding parity was intentionally and explicitly scoped out in the PR description ("TemplateBinding has no ConverterCulture property to propagate"), so re-raising it would be noise.

CI

The remaining red legs (AOT macOS, RunOniOS_MauiRelease/TrimFull/CoreCLR) build generic template apps that do not use ConverterCulture and fail inconsistently across runtime variants (Debug + CoreCLR-Release pass, Mono-Release fails) — a known infra/trim flakiness signature, not attributable to this PR. The relevant build and Helix unit-test legs pass. Ready for human review/merge once the full maui-pr build settles.

Multi-model review (gpt-5.5 · opus-4.8 · opus-4.6 · gemini-3.1-pro). Comments only — not a formal approval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-xaml XAML, CSS, Triggers, Behaviors

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants