feat(ValidateForm): add ValidateAsync method#7469
Conversation
Reviewer's GuideAdds asynchronous validation support to ValidateForm via a new ValidateAsync method, wires it through the internal DataAnnotations validator with TaskCompletionSource coordination, and updates usages, samples, and tests to use the async API while marking the old sync method as obsolete. Sequence diagram for asynchronous form validation with ValidateAsyncsequenceDiagram
actor Developer
participant ValidateForm
participant DataAnnotationsValidator
participant EditContext
participant AsyncValidatorComponent
Developer->>ValidateForm: ValidateAsync()
Activate ValidateForm
ValidateForm->>DataAnnotationsValidator: ValidateAsync()
Activate DataAnnotationsValidator
DataAnnotationsValidator->>DataAnnotationsValidator: create _tcs
DataAnnotationsValidator->>EditContext: Validate()
Note right of EditContext: Triggers validation pipeline
EditContext->>AsyncValidatorComponent: ValidateFieldAsync()
Activate AsyncValidatorComponent
AsyncValidatorComponent-->>EditContext: validation result
Deactivate AsyncValidatorComponent
EditContext->>DataAnnotationsValidator: ValidateModel(...)
Activate DataAnnotationsValidator
DataAnnotationsValidator->>DataAnnotationsValidator: collect validationResults
DataAnnotationsValidator->>EditContext: NotifyValidationStateChanged()
DataAnnotationsValidator->>DataAnnotationsValidator: _tcs.TrySetResult(validationResults.Count == 0)
Deactivate DataAnnotationsValidator
DataAnnotationsValidator-->>ValidateForm: Task<bool> (await _tcs)
Deactivate DataAnnotationsValidator
ValidateForm-->>Developer: bool (combined result)
Deactivate ValidateForm
Class diagram for updated ValidateForm and BootstrapBlazorDataAnnotationsValidatorclassDiagram
class ValidateForm {
+bool Validate() %% obsolete, sync validation
+Task~bool~ ValidateAsync()
}
class BootstrapBlazorDataAnnotationsValidator {
-TaskCompletionSource~bool~ _tcs
+Task~bool~ ValidateAsync()
+bool Validate()
-void AddEditContextDataAnnotationsValidation()
-Task ValidateModel(EditContext editContext, ValidationMessageStore messages, object model, IServiceProvider provider)
-Task ValidateField(EditContext editContext, ValidationMessageStore messages, FieldIdentifier field, IServiceProvider provider)
}
ValidateForm --> BootstrapBlazorDataAnnotationsValidator : uses
BootstrapBlazorDataAnnotationsValidator --> EditContext : validates
BootstrapBlazorDataAnnotationsValidator --> ValidationMessageStore : managesMessages
BootstrapBlazorDataAnnotationsValidator --> FieldIdentifier : validatesField
BootstrapBlazorDataAnnotationsValidator --> IServiceProvider : resolvesServices
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- Consider handling concurrent calls to
ValidateAsynconBootstrapBlazorDataAnnotationsValidatormore defensively (e.g., rejecting a second call while one is in flight or using a new localTaskCompletionSourceper call) to avoid multiple callers sharing the same_tcsinstance and getting unexpected results. - After
ValidateModelcompletes and_tcs.TrySetResult(...)is invoked, you may want to reset_tcstonullto avoid holding onto a completedTaskCompletionSourcelonger than needed and to make misuse (e.g., accidentally reusing the same instance) more obvious.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider handling concurrent calls to `ValidateAsync` on `BootstrapBlazorDataAnnotationsValidator` more defensively (e.g., rejecting a second call while one is in flight or using a new local `TaskCompletionSource` per call) to avoid multiple callers sharing the same `_tcs` instance and getting unexpected results.
- After `ValidateModel` completes and `_tcs.TrySetResult(...)` is invoked, you may want to reset `_tcs` to `null` to avoid holding onto a completed `TaskCompletionSource` longer than needed and to make misuse (e.g., accidentally reusing the same instance) more obvious.
## Individual Comments
### Comment 1
<location> `src/BootstrapBlazor/Components/ValidateForm/BootstrapBlazorDataAnnotationsValidator.cs:55-60` </location>
<code_context>
/// 手动验证表单方法
/// </summary>
/// <returns></returns>
+ internal async Task<bool> ValidateAsync()
+ {
+ _tcs = new(false);
+ var ret = CurrentEditContext.Validate();
+ var valid = await _tcs.Task;
+ return ret && valid;
+ }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid potential races when multiple concurrent ValidateAsync calls share a single TaskCompletionSource field.
Since `_tcs` is a single field, concurrent `ValidateAsync` calls will overwrite each other’s TCS. The last call wins, and earlier callers may await a task that is never (or incorrectly) completed, causing hangs or incorrect validation results when multiple validations run in parallel.
Consider either preventing concurrent `ValidateAsync` calls (e.g., lock/guard/throw) or using a per-call TCS that’s wired into the validation flow instead of a shared field, so behavior remains correct even if calls overlap.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7469 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 749 749
Lines 32912 32922 +10
Branches 4573 4574 +1
=========================================
+ Hits 32912 32922 +10
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR adds asynchronous validation support to the ValidateForm component by introducing a new ValidateAsync method and deprecating the synchronous Validate method. This change enables proper support for components that require asynchronous validation.
Key changes:
- Added
ValidateAsync()method to ValidateForm that supports asynchronous validation - Marked the existing synchronous
Validate()method as obsolete - Updated all internal usages, tests, and samples to use the new async method
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs | Added public ValidateAsync() method and marked Validate() as obsolete |
| src/BootstrapBlazor/Components/ValidateForm/BootstrapBlazorDataAnnotationsValidator.cs | Implemented internal ValidateAsync() with TaskCompletionSource to support async validation flow |
| src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs | Updated InCell validation to use ValidateAsync() |
| test/UnitTest/Components/InputNumberTest.cs | Updated test to use ValidateAsync() instead of Validate() |
| test/UnitTest/Components/DateTimeRangeTest.cs | Updated test to use ValidateAsync() and properly await validation calls |
| test/UnitTest/Components/DateTimePickerTest.cs | Updated test to use ValidateAsync() |
| src/BootstrapBlazor.Server/Components/Samples/ValidateForms.razor.cs | Updated sample code to demonstrate ValidateAsync() usage |
| src/BootstrapBlazor.Server/Components/Samples/Cascaders.razor.cs | Updated sample code to use ValidateAsync() |
| src/BootstrapBlazor/BootstrapBlazor.csproj | Version bumped to 10.2.1-beta05 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// <summary> | ||
| /// 异步验证方法 用于代码调用触发表单验证(支持异步验证) | ||
| /// </summary> | ||
| /// <returns></returns> |
There was a problem hiding this comment.
The XML documentation is incomplete. The returns tag on line 661 is empty and should describe what the method returns. It should specify that it returns a Task that resolves to true if validation succeeds, false otherwise.
| /// <returns></returns> | |
| /// <returns>A Task that resolves to true if validation succeeds; otherwise, false.</returns> |
| private TaskCompletionSource<bool>? _tcs; | ||
| /// <summary> | ||
| /// 手动验证表单方法 | ||
| /// </summary> | ||
| /// <returns></returns> | ||
| internal async Task<bool> ValidateAsync() | ||
| { | ||
| _tcs = new(false); | ||
| var ret = CurrentEditContext.Validate(); | ||
| var valid = await _tcs.Task; | ||
| return ret && valid; | ||
| } |
There was a problem hiding this comment.
The _tcs field is not thread-safe and can cause race conditions. If ValidateAsync() is called multiple times concurrently or before a previous validation completes, the field will be overwritten and the previous caller will await a task that never completes or receives the wrong result. Consider using a local variable for the TaskCompletionSource instead, or implement proper synchronization to prevent concurrent validations.
| /// <returns></returns> | ||
| internal async Task<bool> ValidateAsync() | ||
| { | ||
| _tcs = new(false); |
There was a problem hiding this comment.
The TaskCompletionSource constructor is being called with a boolean argument 'false'. In modern .NET, TaskCompletionSource constructors accept TaskCreationOptions, not a boolean. The boolean parameter doesn't exist. This should likely be 'new TaskCompletionSource()' or 'new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously)' to avoid synchronous continuations.
| _tcs = new(false); | |
| _tcs = new(System.Threading.Tasks.TaskCreationOptions.RunContinuationsAsynchronously); |
| if (_tcs != null) | ||
| { | ||
| _tcs.TrySetResult(validationResults.Count == 0); |
There was a problem hiding this comment.
The _tcs field check and result setting should handle the case where the field might be null. While the current implementation sets _tcs in ValidateAsync(), the ValidateModel method can also be called from OnValidationRequested event handler (line 83) where _tcs would be null. This creates inconsistent behavior - the TaskCompletionSource is only set when validation is triggered via ValidateAsync(), not when triggered by the EditContext's validation events.
| if (_tcs != null) | |
| { | |
| _tcs.TrySetResult(validationResults.Count == 0); | |
| // Safely complete any pending TaskCompletionSource associated with this validation. | |
| var tcs = _tcs; | |
| if (tcs != null) | |
| { | |
| tcs.TrySetResult(validationResults.Count == 0); | |
| _tcs = null; |
| /// 手动验证表单方法 | ||
| /// </summary> | ||
| /// <returns></returns> |
There was a problem hiding this comment.
The XML documentation summary "手动验证表单方法" (Manual form validation method) is insufficient. It should describe what the method returns and clarify that it performs asynchronous validation. Consider adding a returns tag explaining that it returns a Task that resolves to true if validation succeeds, false otherwise.
| /// 手动验证表单方法 | |
| /// </summary> | |
| /// <returns></returns> | |
| /// 手动异步验证表单方法,执行表单的异步验证并返回验证结果。 | |
| /// </summary> | |
| /// <returns>一个 <see cref="Task{Boolean}"/>,当验证完成时为 true 表示验证成功,否则为 false。</returns> |
Link issues
fixes #7467
Summary By Copilot
Regression?
Risk
Verification
Packaging changes reviewed?
☑️ Self Check before Merge
Summary by Sourcery
Add asynchronous form validation support to ValidateForm and update usages to the new API.
New Features:
Enhancements:
Tests: