Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
Expand Down Expand Up @@ -88,9 +88,9 @@ private EventItem[] GetEvents() =>
}
];

private void OnValidate()
private async Task OnValidate()
{
ValidateForm1.Validate();
await ValidateForm1.ValidateAsync();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
Expand Down Expand Up @@ -249,10 +249,9 @@ private void OnValidateReset()
[NotNull]
private ValidateForm? ValidatorForm { get; set; }

private Task OnValidator()
private async Task OnValidator()
{
ValidatorForm.Validate();
return Task.CompletedTask;
await ValidatorForm.ValidateAsync();
}

[NotNull]
Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/BootstrapBlazor.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>10.2.1-beta04</Version>
<Version>10.2.1-beta05</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
3 changes: 2 additions & 1 deletion src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,8 @@ private async Task ClickEditButton(TItem item)
private async Task ClickUpdateButtonCallback()
{
// 验证 InCell 模式下的表单
if (!_inCellValidateForm.Validate())
var valid = await _inCellValidateForm.ValidateAsync();
if (!valid)
{
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
Expand Down Expand Up @@ -47,10 +47,23 @@ protected override void OnInitialized()
AddEditContextDataAnnotationsValidation();
}

private TaskCompletionSource<bool>? _tcs;
/// <summary>
/// 手动验证表单方法
/// </summary>
/// <returns></returns>
Comment on lines 52 to 54
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
/// 手动验证表单方法
/// </summary>
/// <returns></returns>
/// 手动异步验证表单方法,执行表单的异步验证并返回验证结果。
/// </summary>
/// <returns>一个 <see cref="Task{Boolean}"/>,当验证完成时为 true 表示验证成功,否则为 false。</returns>

Copilot uses AI. Check for mistakes.
internal async Task<bool> ValidateAsync()
{
_tcs = new(false);
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
_tcs = new(false);
_tcs = new(System.Threading.Tasks.TaskCreationOptions.RunContinuationsAsynchronously);

Copilot uses AI. Check for mistakes.
var ret = CurrentEditContext.Validate();
var valid = await _tcs.Task;
return ret && valid;
}
Comment on lines +50 to +61
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.

/// <summary>
/// 手动验证表单方法
/// </summary>
[ExcludeFromCodeCoverage]
internal bool Validate() => CurrentEditContext.Validate();

private void AddEditContextDataAnnotationsValidation()
Expand Down Expand Up @@ -93,6 +106,11 @@ private async Task ValidateModel(EditContext editContext, ValidationMessageStore
}
}
editContext.NotifyValidationStateChanged();

if (_tcs != null)
{
_tcs.TrySetResult(validationResults.Count == 0);
Comment on lines +110 to +112
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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;

Copilot uses AI. Check for mistakes.
}
}

private async Task ValidateField(EditContext editContext, ValidationMessageStore messages, FieldIdentifier field, IServiceProvider provider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -649,11 +649,18 @@ private async Task OnInvalidSubmitForm(EditContext context)
private EditContext? _formlessEditContext;

/// <summary>
/// 验证方法 用于代码调用触发表单验证
/// 同步验证方法 用于代码调用触发表单验证(不支持某些组件的异步验证)
/// </summary>
/// <returns></returns>
[Obsolete("已弃用,请使用 ValidateAsync 方法。Deprecated. Please use the ValidateAsync method.")]
[ExcludeFromCodeCoverage]
public bool Validate() => Validator.Validate();

/// <summary>
/// 异步验证方法 用于代码调用触发表单验证(支持异步验证)
/// </summary>
/// <returns></returns>
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
/// <returns></returns>
/// <returns>A Task that resolves to true if validation succeeds; otherwise, false.</returns>

Copilot uses AI. Check for mistakes.
public Task<bool> ValidateAsync() => Validator.ValidateAsync();

/// <summary>
/// 通知属性改变方法
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion test/UnitTest/Components/DateTimePickerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,7 @@ await cut.InvokeAsync(() =>
Assert.Equal("02/15/2024", model.DateTime.Value.ToString("MM/dd/yyyy"));

// 录入非法数值 Validate 通过
var valid = cut.Instance.Validate();
var valid = await cut.InvokeAsync(cut.Instance.ValidateAsync);
Assert.True(valid);
}

Expand Down
8 changes: 3 additions & 5 deletions test/UnitTest/Components/DateTimeRangeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -479,13 +479,12 @@ public async Task InValidateForm_Ok()
cut.Contains("class=\"form-label\"");

// 验证
var validate = true;
await cut.InvokeAsync(() => validate = cut.Instance.Validate());
var validate = await cut.InvokeAsync(cut.Instance.ValidateAsync);
Assert.False(validate);

var range = cut.FindComponent<DateTimeRange>();
var clear = range.Find(".is-clear");
clear.Click();
await cut.InvokeAsync(() => clear.Click());

range.Render(pb =>
{
Expand All @@ -504,8 +503,7 @@ public async Task InValidateForm_Ok()
{
pb.Add(a => a.Model, foo);
});
validate = false;
await cut.InvokeAsync(() => validate = cut.Instance.Validate());
validate = await cut.InvokeAsync(cut.Instance.ValidateAsync);
Assert.True(validate);
}

Expand Down
6 changes: 3 additions & 3 deletions test/UnitTest/Components/InputNumberTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,15 +242,15 @@ await cut.InvokeAsync(() =>
});
Assert.Equal(1, model.Count);

var valid = cut.Instance.Validate();
var valid = await cut.InvokeAsync(cut.Instance.ValidateAsync);
Assert.False(valid);

await cut.InvokeAsync(() =>
{
input.Change("t2");
});
Assert.Equal(1, model.Count);
valid = cut.Instance.Validate();
valid = await cut.InvokeAsync(cut.Instance.ValidateAsync);
Assert.False(valid);

await cut.InvokeAsync(() =>
Expand All @@ -259,7 +259,7 @@ await cut.InvokeAsync(() =>
});
Assert.Equal(2, model.Count);

valid = cut.Instance.Validate();
valid = await cut.InvokeAsync(cut.Instance.ValidateAsync);
Assert.True(valid);
}

Expand Down
Loading