Skip to content

Add message template formatting to DataAnnotations validation attributes - #132764

Merged
ViveliDuCh merged 3 commits into
mainfrom
api-proposal/dataannotations-format-message
Aug 27, 2026
Merged

Add message template formatting to DataAnnotations validation attributes#132764
ViveliDuCh merged 3 commits into
mainfrom
api-proposal/dataannotations-format-message

Conversation

@ViveliDuCh

Copy link
Copy Markdown
Member

Summary

Adds the approved ValidationAttribute.FormatMessage API so callers can supply externally localized composite-format strings while each validation attribute remains responsible for providing its placeholder arguments.

Changes

  • Add ValidationAttribute.FormatMessage([StringSyntax("CompositeFormat")] string format, string name).
  • Route the base FormatErrorMessage implementation through the new method.
  • Add overrides for CompareAttribute, FileExtensionsAttribute, LengthAttribute, MaxLengthAttribute, MinLengthAttribute, RangeAttribute, RegularExpressionAttribute, and StringLengthAttribute.
  • Preserve existing placeholder ordering and CurrentCulture formatting.
  • Add coverage for supplied formats, virtual dispatch, culture-sensitive formatting, invalid formats, and null arguments.

Existing FormatErrorMessage behavior remains unchanged.

Testing

  • dotnet build — succeeded with 0 warnings and 0 errors.
  • dotnet build /t:test .\tests\System.ComponentModel.Annotations.Tests.csproj — 1,008 passed, 0 failed, 0 skipped.

Fixes#132605

Note

This pull request description was generated with GitHub Copilot.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-componentmodel-dataannotations
See info in area-owners.md if you want to be subscribed.

CopilotAI 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.

Pull request overview

Adds a new public formatting primitive to DataAnnotations validation attributes so callers can supply an external composite-format template while each attribute remains responsible for projecting its own placeholder arguments.

Changes:

  • Add ValidationAttribute.FormatMessage([StringSyntax("CompositeFormat")] string format, string name) and route the base FormatErrorMessage implementation through it.
  • Override FormatMessage in built-in attributes that require additional placeholders (e.g., {1}, {2}), preserving existing argument ordering and CurrentCulture formatting behavior.
  • Add/extend tests to cover supplied formats, virtual dispatch, culture-sensitive formatting, and invalid/null formats.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/ValidationAttribute.csIntroduces FormatMessage and updates base FormatErrorMessage to delegate through it.
src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/CompareAttribute.csOverrides FormatMessage to supply the other-property display name argument.
src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/FileExtensionsAttribute.csOverrides FormatMessage to supply normalized/dot-prefixed extensions.
src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/LengthAttribute.csOverrides FormatMessage to supply min/max length placeholders.
src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/MaxLengthAttribute.csOverrides FormatMessage to supply the max length placeholder.
src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/MinLengthAttribute.csOverrides FormatMessage to supply the min length placeholder.
src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/RangeAttribute.csOverrides FormatMessage to supply converted min/max placeholders.
src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/RegularExpressionAttribute.csOverrides FormatMessage to supply the regex pattern placeholder.
src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/StringLengthAttribute.csOverrides FormatMessage to supply maximum/minimum length placeholders and keeps default-template selection in FormatErrorMessage.
src/libraries/System.ComponentModel.Annotations/ref/System.ComponentModel.Annotations.csUpdates public surface area (ref) to include the new virtual and related overrides.
src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/ValidationAttributeTests.csAdds base-level tests for FormatMessage, null/invalid format behavior, and FormatErrorMessage virtual dispatch.
src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/CompareAttributeTests.csAdds coverage for CompareAttribute.FormatMessage argument projection and display-name behavior.
src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/FileExtensionsAttributeTests.csAdds coverage for FileExtensionsAttribute.FormatMessage using normalized extensions.
src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/LengthAttributeTests.csAdds coverage for LengthAttribute.FormatMessage placeholder projection.
src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/MaxLengthAttributeTests.csAdds coverage for MaxLengthAttribute.FormatMessage placeholder projection.
src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/MinLengthAttributeTests.csAdds coverage for MinLengthAttribute.FormatMessage placeholder projection.
src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/RangeAttributeTests.csAdds coverage for RangeAttribute.FormatMessage conversion and culture-sensitive formatting.
src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/RegularExpressionAttributeTests.csAdds coverage for RegularExpressionAttribute.FormatMessage supplying the pattern.
src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/StringLengthAttributeTests.csAdds coverage for StringLengthAttribute.FormatMessage placeholder projection and FormatErrorMessage behavior.

Add the approved ValidationAttribute.FormatMessage API for caller-supplied composite formats and project built-in attribute arguments through overrides.
Cover culture, virtual dispatch, error paths, and source/ref naming consistency.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 524df8bb-0555-4425-b73f-bc8340d0dfc2
CopilotAI review requested due to automatic review settings August 25, 2026 21:54
@ViveliDuCh
ViveliDuChforce-pushed the api-proposal/dataannotations-format-message branch from db016ee to c3ab07bCompareAugust 25, 2026 21:54

CopilotAI 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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

@tarekgh

Copy link
Copy Markdown
Member

Should we also update the validation attribute replicas emitted by Microsoft.Extensions.Options/gen/Emitter.cs: CompareAttribute, LengthAttribute, MaxLengthAttribute, MinLengthAttribute, and RangeAttribute? They currently inherit the base FormatMessage, which supplies only {0}. When the target exposes the new API, these replicas should conditionally override FormatMessage with the same [StringSyntax(CompositeFormat)] annotation and attribute-specific arguments. Otherwise, they do not preserve the new built-in attribute contract.

CopilotAI review requested due to automatic review settings August 26, 2026 02:07

CopilotAI 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.

Pull request overview

Copilot reviewed 30 out of 30 changed files in this pull request and generated 2 comments.

- Detect FormatMessage support from target compilation symbols
- Preserve attribute-specific arguments in generated replicas
- Share Range initialization between validation and formatting
- Add deterministic baselines and runtime regression coverage
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 524df8bb-0555-4425-b73f-bc8340d0dfc2
CopilotAI review requested due to automatic review settings August 26, 2026 04:51
@ViveliDuCh
ViveliDuChforce-pushed the api-proposal/dataannotations-format-message branch from 6ec2050 to 43ba3c4CompareAugust 26, 2026 04:51

CopilotAI 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.

Pull request overview

Copilot reviewed 30 out of 30 changed files in this pull request and generated 2 comments.

Comment threadsrc/libraries/Microsoft.Extensions.Options/gen/Emitter.cs Outdated

@oroztociloroztocil left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good.

I don't have a strong opinion about the added argument null checks. They are redundant, but also mean the exception is thrown at the level of the public contract. Considering the existing FormatErrorMessage did not have them, I would probably remove them from FormatMessage too.

Rely on string.Format to validate null format strings, use a valid RangeAttribute in null-format coverage, and update generated baselines.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 524df8bb-0555-4425-b73f-bc8340d0dfc2
CopilotAI review requested due to automatic review settings August 26, 2026 15:54

CopilotAI 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.

Pull request overview

Copilot reviewed 30 out of 30 changed files in this pull request and generated no new comments.

@tarekghtarekgh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, Thanks!

@ViveliDuCh

Copy link
Copy Markdown
MemberAuthor

/ba-g Remaining unclassified failures are the CompositeMLDsaCngTests Windows failures tracked by known issue #132830, unrelated to this PR.

@ViveliDuCh
ViveliDuCh disabled auto-merge August 27, 2026 19:29
@ViveliDuCh
ViveliDuCh merged commit c365a0b into mainAug 27, 2026
78 of 80 checks passed
@ViveliDuCh
ViveliDuCh deleted the api-proposal/dataannotations-format-message branch August 27, 2026 19:30
@ViveliDuCh

Copy link
Copy Markdown
MemberAuthor

/backport to release/11.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/11.0 (link to workflow run)

@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Aug 28, 2026
artl93 pushed a commit that referenced this pull request Aug 28, 2026
…idation attributes (#132853)
Backport of #132764 to `release/11.0`.
API proposal: #132605
/cc @jeffhandley@oroztocil
## Customer Impact
- [x] Customer reported
- [ ] Found internally
.NET 11 introduces first-class localization support in
`Microsoft.Extensions.Validation`, consumed by Minimal APIs and Blazor.
However, DataAnnotations does not expose a shared way for a caller to
supply a localized message template while allowing each validation
attribute to provide its own placeholder arguments.
Without this change, the .NET 11 localization implementation must retain
generated per-attribute formatting switches, other hosts cannot reuse
the same primitive, and duplicated formatting knowledge can diverge from
the real attribute behavior. One existing example is
`FileExtensionsAttribute`, where the attribute supplies normalized,
dot-prefixed extensions while duplicated upper-layer formatters have
supplied the raw property value.
The customer scenario has been requested repeatedly:
- dotnet/aspnetcore#4848
- dotnet/aspnetcore#12158
- #72469
- dotnet/aspnetcore#66392
This backport adds the API-approved `ValidationAttribute.FormatMessage`
primitive and preserves the attribute-specific formatting contract for
built-in attributes and Options source-generated replicas.
We believe this meets the .NET 11 RC2 bug bar as:
- Significant customer feedback on a feature new in the release.
- A significantly incomplete message-presentation scenario in
DataAnnotations that affects the new .NET 11 validation stack, including
localization in MEV, Minimal APIs, and Blazor.
## Regression
- [ ] Yes
- [x] No
This is not a regression from .NET 10. The API and the first-class
`Microsoft.Extensions.Validation` localization scenario are new in .NET
11.
## Testing
On `main`:
- `System.ComponentModel.Annotations` built successfully.
- `System.ComponentModel.Annotations.Tests`: 1,008 passed, 0 failed.
- `Microsoft.Extensions.Options` built successfully.
- `Microsoft.Extensions.Options.Tests`: 177 net11 tests and 158 net481
tests passed.
- `Microsoft.Extensions.Options.SourceGeneration.Unit.Tests`: 103 net11
tests and 82 net481 tests passed, with one expected conditional skip.
- `Microsoft.Extensions.Options.SourceGeneration.Tests`: 68 net11 tests
and 68 net481 tests passed.
- Generated replicas were compiled and executed for C# 10 and C# 11.
- The .NET Framework generated output remains unchanged.
The same component tests and release-branch CI will validate the
backport on `release/11.0`.
## Risk
Low-to-moderate.
The change adds an API-reviewed public virtual method and explicit
overrides on the built-in attributes that require additional format
arguments. It is additive and removes no existing surface.
Existing `FormatErrorMessage` behavior, argument ordering, and
`CurrentCulture` formatting remain unchanged. The Options source
generator emits matching overrides only when the target compilation
exposes the new API, leaving down-level and .NET Framework output
unchanged.
The main implementation risks are public virtual dispatch and
source-generator parity. These are mitigated by:
- API approval in #132605.
- Technical review and approval on #132764.
- Source/ref signature checks.
- Direct tests of virtual dispatch and every built-in argument mapping.
- Compile-load-invoke tests for generated replicas.
- Complete net11 and net481 source-generator test coverage.
No package references, dependencies, or package-authoring project
settings change.
> [!NOTE]
> This pull request description was generated with GitHub Copilot.
Co-authored-by: Viviana Dueñas <50237907+ViveliDuCh@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 524df8bb-0555-4425-b73f-bc8340d0dfc2
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal] Message template formatting support for DataAnnotations validation attributes

5 participants

@ViveliDuCh@tarekgh@drieseng@oroztocil