Add async validation support for System.ComponentModel.DataAnnotations#128656
Add async validation support for System.ComponentModel.DataAnnotations#128656ViveliDuCh wants to merge 6 commits into
Conversation
Implements the approved API shape from dotnet#128096. - Add AsyncValidationAttribute with abstract IsValidAsync returning Task<ValidationResult?> and abstract override IsValid for sync fallback. - Add IAsyncValidatableObject extending IValidatableObject with ValidateAsync returning IAsyncEnumerable<ValidationResult>; no DIM, implementors must provide both Validate and ValidateAsync explicitly. - Add 8 Validator.*Async methods (TryValidateObject/Property/Value, ValidateObject/Property/Value) all returning Task/Task<bool>. - Async pipeline validates properties in parallel using Task.WhenAny with linked CancellationTokenSource for cooperative breakOnFirstError cancellation, matching sync behavior parity. - Update ref assembly, csproj, and ValidationContext.Items XML docs. - Add comprehensive tests covering all async methods, cancellation, breakOnFirstError, mixed sync/async attributes, and IAsyncValidatableObject.
|
Tagging subscribers to this area: @dotnet/area-system-componentmodel-dataannotations |
There was a problem hiding this comment.
Pull request overview
This PR adds a new async validation surface to System.ComponentModel.DataAnnotations by introducing AsyncValidationAttribute, IAsyncValidatableObject, and async counterparts to the Validator APIs, plus tests and a small documentation update for ValidationContext.Items.
Changes:
- Add
AsyncValidationAttributeandIAsyncValidatableObjectas new public types to support async validation patterns. - Add async
ValidatorAPIs and implement an async validation pipeline with parallel execution for property/async-attribute validation. - Add unit tests for the new async APIs and update
ValidationContext.Itemsdocs for parallel async validation scenarios.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/Validator.cs | Implements async Validator APIs and async/parallel validation pipeline. |
| src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/AsyncValidationAttribute.cs | Introduces base type for async validation attributes with IsValidAsync + GetValidationResultAsync. |
| src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/IAsyncValidatableObject.cs | Introduces async object-level validation contract (ValidateAsync). |
| src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/ValidationContext.cs | Documents Items thread-safety expectations under parallel async validation. |
| src/libraries/System.ComponentModel.Annotations/ref/System.ComponentModel.Annotations.cs | Adds new public API surface to the ref assembly (needs ordering consistency fix). |
| src/libraries/System.ComponentModel.Annotations/src/System.ComponentModel.Annotations.csproj | Adds new source files to the project. |
| src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/ValidatorTests.cs | Adds tests for async Validator APIs (includes timing-based parallelism assertions). |
| src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/ValidationAttributeTests.cs | Adds tests for AsyncValidationAttribute behaviors (sync fallback vs async path, formatting, cancellation). |
…timing-based parallelism tests with deterministic concurrency probes
…ssage into base class, use constructed generic types in XML doc crefs, and add missing exception documentation
…ontinuation scheduling
…drain remaining tasks in finally to avoid leaks on exception paths, and clarify RequiresValidationContext scope in XML docs
…ValidationContext overload
- Add IAsyncValidateOptions<T> and IAsyncStartupValidator interfaces - Add AsyncValidateOptions<T, TDep1..TDep5> lambda-based validators - Add async Validate overloads on OptionsBuilder<T> (0-5 dependencies) - Extend ValidateOnStart to prefer IAsyncStartupValidator when available - Add DataAnnotationValidateOptionsAsync<T> and ValidateDataAnnotationsAsync extension - Wire IAsyncStartupValidator into Host.StartAsync
- Add IAsyncValidateOptions<T> and IAsyncStartupValidator interfaces - Add AsyncValidateOptions<T, TDep1..TDep5> lambda-based validators - Add async Validate overloads on OptionsBuilder<T> (0-5 dependencies) - Extend ValidateOnStart to prefer IAsyncStartupValidator when available - Add DataAnnotationValidateOptionsAsync<T> and ValidateDataAnnotationsAsync extension - Wire IAsyncStartupValidator into Host.StartAsync
| /// Provides a way for an object to be validated asynchronously. | ||
| /// Inherits from <see cref="IValidatableObject"/>. Implementors must provide both | ||
| /// <see cref="IValidatableObject.Validate"/> and <see cref="ValidateAsync"/>. | ||
| /// </summary> |
There was a problem hiding this comment.
Worth documenting the dispatch contract explicitly here — there's already a test (TryValidateObjectAsync_IAsyncValidatableObject_Preferred_Over_IValidatableObject) asserting this behavior, but a user implementing both methods on the same type has no way to know Validate will be skipped on the async path without reading the Validator source.
| /// </summary> | |
| /// <summary> | |
| /// Provides a way for an object to be validated asynchronously. | |
| /// Inherits from <see cref="IValidatableObject"/>. Implementors must provide both | |
| /// <see cref="IValidatableObject.Validate"/> and <see cref="ValidateAsync"/>. | |
| /// </summary> | |
| /// <remarks> | |
| /// When an object implements both <see cref="IAsyncValidatableObject"/> and | |
| /// <see cref="IValidatableObject"/>, the asynchronous <see cref="Validator"/> APIs | |
| /// (such as <see cref="Validator.TryValidateObjectAsync(object, ValidationContext, System.Collections.Generic.ICollection{ValidationResult}?, System.Threading.CancellationToken)"/>) | |
| /// invoke only <see cref="ValidateAsync"/>; <see cref="IValidatableObject.Validate"/> | |
| /// is not called as part of the async pipeline. The synchronous <see cref="Validator"/> | |
| /// APIs continue to invoke <see cref="IValidatableObject.Validate"/>. | |
| /// </remarks> |
Fixes #128096
Implements async validation support for
System.ComponentModel.DataAnnotationsas approved in API review.What's included
AsyncValidationAttribute— abstract base class deriving fromValidationAttributefor async validation scenarios (database lookups, API calls).IsValidisabstract overrideso subclasses must decide how to handle synchronous callers.IsValidAsyncis the primary async entry point.GetValidationResultAsyncmirrors the syncGetValidationResultwith error message formatting.IAsyncValidatableObject— interface extendingIValidatableObjectfor object-level async validation viaIAsyncEnumerable<ValidationResult>. No default interface method — implementors provide bothValidateandValidateAsync.Validatormethods —TryValidateObjectAsync(2 overloads),TryValidatePropertyAsync,TryValidateValueAsync,ValidateObjectAsync(2 overloads),ValidatePropertyAsync,ValidateValueAsync. All returnTask/Task<bool>.ValidationContext.ItemsXML doc remark about thread safety under concurrent async validationValidatormethods, mixed sync/async attributes,breakOnFirstErrorwith parallel async, cancellation propagation,IAsyncValidatableObject, class-level async attributes, error message formatting, and sync fallback viaIsValidoverrideImplementation notes
IAsyncValidatableObject/IValidatableObjectTask.WhenAnywith a linkedCancellationTokenSourcefor cooperativebreakOnFirstErrorcancellationbreakOnFirstErrorbehavior is consistent between sync and async pathsAPI review decisions reflected
Task/Task<T>return types (notValueTask)IsValidasabstract overrideIAsyncValidatableObject