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
Expand Up @@ -10,7 +10,7 @@ namespace BootstrapBlazor.Components;
/// <summary>
/// BootstrapBlazorDataAnnotationsValidator 验证组件
/// </summary>
public class BootstrapBlazorDataAnnotationsValidator : ComponentBase
public class BootstrapBlazorDataAnnotationsValidator : ComponentBase, IDisposable
{
/// <summary>
/// 获得/设置 当前编辑数据上下文
Expand All @@ -23,30 +23,110 @@ public class BootstrapBlazorDataAnnotationsValidator : ComponentBase
/// 获得/设置 当前编辑窗体上下文
/// </summary>
[CascadingParameter]
[NotNull]
private ValidateForm? ValidateForm { get; set; }

[Inject]
[NotNull]
private IServiceProvider? Provider { get; set; }

[NotNull]
private ValidationMessageStore? _message = null;
Comment on lines +26 to +34
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Initialization of _message may be redundant due to OnInitialized logic.

Since _message is always set in OnInitialized, consider making it non-nullable and initializing it there, or use late initialization to prevent null reference confusion.

Suggested implementation:

    [NotNull]
    private ValidationMessageStore _message;

You must ensure that _message is always initialized in the OnInitialized method (or wherever the initialization logic is guaranteed to run before use). For example:

protected override void OnInitialized()
{
    base.OnInitialized();
    _message = new ValidationMessageStore( /* appropriate parameters */ );
}

Remove any redundant initializations of _message elsewhere in the class.


/// <summary>
/// 初始化方法
/// </summary>
protected override void OnInitialized()
{
if (ValidateForm == null)
{
throw new InvalidOperationException($"{nameof(Components.BootstrapBlazorDataAnnotationsValidator)} requires a cascading " +
$"parameter of type {nameof(Components.ValidateForm)}. For example, you can use {nameof(Components.BootstrapBlazorDataAnnotationsValidator)} " +
$"inside an {nameof(Components.ValidateForm)}.");
throw new InvalidOperationException($"{nameof(BootstrapBlazorDataAnnotationsValidator)} requires a cascading parameter of type {nameof(Components.ValidateForm)}. For example, you can use {nameof(BootstrapBlazorDataAnnotationsValidator)} inside an {nameof(Components.ValidateForm)}.");
}

CurrentEditContext.AddEditContextDataAnnotationsValidation(ValidateForm, Provider);
_message = new ValidationMessageStore(CurrentEditContext);
AddEditContextDataAnnotationsValidation();
}

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

private void AddEditContextDataAnnotationsValidation()
{
CurrentEditContext.OnValidationRequested += OnValidationRequested;
CurrentEditContext.OnFieldChanged += OnFieldChanged;
}

private void RemoveEditContextDataAnnotationsValidation()
{
CurrentEditContext.OnValidationRequested -= OnValidationRequested;
CurrentEditContext.OnFieldChanged -= OnFieldChanged;
}

private void OnValidationRequested(object? sender, ValidationRequestedEventArgs args)
{
_ = ValidateModel(CurrentEditContext, _message, Provider);
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Asynchronous validation is not awaited, which may cause race conditions.

ValidateModel and ValidateField are async but not awaited, which may lead to out-of-order validation or missed exceptions. Make the event handlers async and await these calls for correct execution.

}

private void OnFieldChanged(object? sender, FieldChangedEventArgs args)
{
_ = ValidateField(CurrentEditContext, _message, args.FieldIdentifier, Provider);
}

private async Task ValidateModel(EditContext editContext, ValidationMessageStore messages, IServiceProvider provider)
{
var validationContext = new ValidationContext(editContext.Model, provider, null);
var validationResults = new List<ValidationResult>();
await ValidateForm.ValidateObject(validationContext, validationResults);

messages.Clear();
foreach (var validationResult in validationResults.Where(v => !string.IsNullOrEmpty(v.ErrorMessage)))
{
foreach (var memberName in validationResult.MemberNames)
{
if (!string.IsNullOrEmpty(memberName))
{
messages.Add(editContext.Field(memberName), validationResult.ErrorMessage!);
}
}
}
editContext.NotifyValidationStateChanged();
}

private async Task ValidateField(EditContext editContext, ValidationMessageStore messages, FieldIdentifier field, IServiceProvider provider)
{
// 获取验证消息
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(field.Model, provider, null)
{
MemberName = field.FieldName,
DisplayName = field.GetDisplayName()
};

await ValidateForm.ValidateFieldAsync(validationContext, validationResults);

messages.Clear(field);
messages.Add(field, validationResults.Where(v => !string.IsNullOrEmpty(v.ErrorMessage)).Select(result => result.ErrorMessage!));

editContext.NotifyValidationStateChanged();
}

private void Dispose(bool disposing)
{
if (disposing)
{
RemoveEditContextDataAnnotationsValidation();
}
}

/// <summary>
/// <inheritdoc/>
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

This file was deleted.

Loading