Add .NET support for client-side validation for Blazor SSR#66441
Add .NET support for client-side validation for Blazor SSR#66441
Conversation
bf49a46 to
3141a7b
Compare
There was a problem hiding this comment.
Pull request overview
Adds .NET-side support for Blazor SSR client-side validation by emitting unobtrusive data-val-* attributes from System.ComponentModel.DataAnnotations, plus message/summary hooks for a companion JS library.
Changes:
- Introduces
Microsoft.AspNetCore.Components.Forms.ClientValidationAPIs and a default implementation that reflects over validation attributes and caches generateddata-val-*dictionaries. - Activates SSR-only client validation via
DataAnnotationsValidatorandEditContext.Properties, and merges generated attributes intoInputBase(including radio groups). - Updates
ValidationMessage/ValidationSummaryrendering to outputdata-valmsg-*markers, and adds unit tests for the default attribute generator.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Components/Web/src/Forms/ValidationSummary.cs | Adds SSR client-validation summary container (data-valmsg-summary) rendering. |
| src/Components/Web/src/Forms/ValidationMessage.cs | Adds SSR client-validation message placeholder (data-valmsg-for, data-valmsg-replace) rendering. |
| src/Components/Web/src/Forms/InputRadioGroup.cs | Extracts data-val-* attributes from group and passes them to child radios. |
| src/Components/Web/src/Forms/InputRadioContext.cs | Adds cascading storage for client validation attributes for radio children. |
| src/Components/Web/src/Forms/InputRadio.cs | Applies group-provided data-val-* attributes to each rendered radio input. |
| src/Components/Web/src/Forms/InputBase.cs | Merges generated data-val-* attributes into AdditionalAttributes when client validation is enabled. |
| src/Components/Forms/test/DefaultClientValidationServiceTest.cs | Adds unit tests for attribute generation/caching/custom adapters. |
| src/Components/Forms/src/PublicAPI.Unshipped.txt | Declares new public APIs for client validation + new EnableClientValidation parameter. |
| src/Components/Forms/src/DataAnnotationsValidator.cs | Enables SSR-only client validation by storing IClientValidationService in EditContext.Properties. |
| src/Components/Forms/src/ClientValidation/IClientValidationService.cs | New public service contract for generating data-val-* attributes. |
| src/Components/Forms/src/ClientValidation/IClientValidationAdapter.cs | New public extensibility interface for custom validation attributes. |
| src/Components/Forms/src/ClientValidation/DefaultClientValidationService.cs | Default reflection-based generator with caching for supported validation attributes. |
| src/Components/Forms/src/ClientValidation/ClientValidationServiceCollectionExtensions.cs | DI extension to register the default client validation service. |
| src/Components/Forms/src/ClientValidation/ClientValidationContext.cs | Context passed to custom adapters for emitting attributes. |
| _subscriptions?.Dispose(); | ||
| _subscriptions = null; | ||
|
|
||
| // Clean up the client validation service reference from EditContext | ||
| CurrentEditContext?.Properties.Remove(typeof(IClientValidationService)); | ||
|
|
There was a problem hiding this comment.
DataAnnotationsValidator unconditionally removes the IClientValidationService key from EditContext.Properties on dispose. If something else placed a service under the same key (or if multiple validators somehow target the same EditContext), this can remove a value that this instance didn’t set. Consider tracking whether this instance added the entry and/or only removing when the stored value matches the injected ClientValidationService instance.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Description
This PR adds support for rendering client-side validation metadata from .NET validation attributes on Blazor SSR forms. This is the .NET-side part of the overall client-side form validation feature for Blazor SSR. It generates
data-val-*HTML attributes fromSystem.ComponentModel.DataAnnotationsvalidation attributes so the companion JS library (in a separate PR) can enforce them client-side.Contributes to #51040
Companion PR to #66420
What this enables
When a Blazor SSR form uses
<DataAnnotationsValidator />, input components are automatically rendered withdata-val-*attributes derived from model validation attributes ([Required],[Range],[EmailAddress], etc.). The companion JS library reads these attributes and provides instant, in-browser validation feedback without a server round-trip.Client validation is activated automatically for SSR forms - no additional configuration is needed beyond the standard
<DataAnnotationsValidator />component that developers already use.How it works
flowchart LR subgraph Server["Server (.NET)"] Model["<b>Model</b><br/>[Required]<br/>[Range] etc."] Service["<b>DefaultClient<br/>ValidationService</b><br/>reflection + caching"] Components["<b>InputBase</b> / <b>InputText</b><br/><b>InputRadioGroup</b><br/><b>ValidationMessage</b><br/><b>ValidationSummary</b>"] Model --> Service Service -->|"generates<br/>data-val-* dict"| Components end subgraph HTML["Rendered HTML"] Input["input data-val='true'<br/>data-val-required='...'"] Msg["div data-valmsg-for='...'"] Summary["div data-valmsg-summary='true'"] end Components --> Input Components --> Msg Components --> SummaryDataAnnotationsValidatordetects SSR mode (AssignedRenderMode is null) and stores theIClientValidationServiceintoEditContext.Properties. This acts as a signal for child components to render client validation attributes.InputBase(and all derived components likeInputText,InputNumber, etc.) reads the service fromEditContext.Propertieson each render, callsGetClientValidationAttributes(fieldIdentifier), and merges the resultingdata-val-*attributes intoAdditionalAttributes.InputRadioGroup/InputRadio- the group component extractsdata-val-*attributes and passes them to child radio buttons viaInputRadioContext. All radio buttons in the group receive the attributes; the JS library deduplicates by tracking one radio per name group.ValidationMessagerendersdata-valmsg-for="{fieldName}"anddata-valmsg-replace="true"so the JS library can find and update the error message element.ValidationSummaryrendersdata-valmsg-summary="true"so the JS library can find and populate the summary container.sequenceDiagram participant DAV as DataAnnotationsValidator participant EC as EditContext.Properties participant IB as InputBase / InputText participant DCVS as DefaultClientValidationService Note over DAV: SSR render mode only DAV->>EC: Store IClientValidationService IB->>EC: Read IClientValidationService IB->>DCVS: GetClientValidationAttributes(fieldIdentifier) DCVS-->>IB: { "data-val": "true", "data-val-required": "..." } IB->>IB: Merge into AdditionalAttributes (first-wins)Key design decisions
SSR-only activation. Client validation attributes are only rendered when
AssignedRenderMode is null(static SSR). Interactive Blazor components useEditContextfor server-side validation - addingdata-val-*attributes would be redundant and could conflict with interactive validation state.EditContext.Properties as communication channel. Rather than adding a new cascading parameter or modifying
InputBase's parameter signature (which would be a breaking change), theIClientValidationServiceis stored inEditContext.Properties. This is an existing extensibility mechanism that doesn't require changes to component contracts.First-wins attribute merging. When
InputBasemergesdata-val-*attributes intoAdditionalAttributes, it usesTryAdd(first-wins). This means developer-specified attributes take precedence - if a developer manually setsdata-val-requiredinAdditionalAttributes, the generated value is not overwritten.Singleton with caching.
DefaultClientValidationServiceis registered as a singleton and caches attribute dictionaries per(Type, FieldName). Validation attributes are read from reflection once and reused across all requests.Supported validation attributes
DefaultClientValidationServicegeneratesdata-val-*attributes for these standardSystem.ComponentModel.DataAnnotationsattributes:data-val-*attributes[Required]data-val-required[StringLength]data-val-length,-min,-max[MaxLength]data-val-maxlength,-max[MinLength]data-val-minlength,-min[Range]data-val-range,-min,-max[RegularExpression]data-val-regex,-pattern[Compare]data-val-equalto,-other[EmailAddress]data-val-email[Url]data-val-url[Phone]data-val-phone[CreditCard]data-val-creditcard[FileExtensions]data-val-fileextensions,-extensionsIClientValidationAdapterNew public API surface
New namespace:
Microsoft.AspNetCore.Components.Forms.ClientValidationExisting namespace:
Microsoft.AspNetCore.Components.FormsExisting namespace:
Microsoft.Extensions.DependencyInjectionCustom validation attribute example
Usage
No special setup is needed for standard Blazor SSR apps. Client validation activates automatically when
<DataAnnotationsValidator />is present in an SSR form:Rendered HTML (SSR):
To disable client validation for a specific form:
Testing
25 unit tests covering:
data-val-*generationStringLengthwith max-only (verifiesminattribute is omitted whenMinimumLengthis 0)Rangewithdoublevalues (verifies invariant culture formatting)null)null)[Display]and[DisplayName]integration in error messagesIClientValidationAdapterimplementationBlazor SSR E2E tests will be provided either in a follow up PR, or added to this PR once the JS library PR is merged.