Skip to content

Commit 4a8f80a

Browse files
AiYuZhenArgoZhang
andauthored
fix(ValidateForm): validate all validation from MetadataType (#4360)
* 修复 MetadataType 方式继承 IValidateCollection 接口验证后又对其中涉及的属性进行了单独验证,在特定情况下会忽略单独验证的错误提示。#4359 * fix: 更新 ValidateForm 验证逻辑 * refactor: 重构代码 * refactor: 更改 IValidateCollection 接口方法名称 * 调整代码以通过验证。 * doc: 增加注释文档 * refactor: 精简代码 * test: 更新单元测试 * chore: bump version 8.9.4-beta06 --------- Co-authored-by: Argo <argo@live.ca>
1 parent 3cb9837 commit 4a8f80a

File tree

7 files changed

+156
-50
lines changed

7 files changed

+156
-50
lines changed

src/BootstrapBlazor.Server/Components/Samples/ValidateForms.razor.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,12 @@ public IEnumerable<ValidationResult> Validate(ValidationContext validationContex
163163
_validMemberNames.AddRange([nameof(model.Email), nameof(model.ConfirmEmail)]);
164164
}
165165
}
166-
return InvalidMemberNames();
166+
return GetInvalidMemberNames();
167167
}
168168

169-
public List<string> ValidMemberNames() => _validMemberNames;
169+
public List<string> GetValidMemberNames() => _validMemberNames;
170170

171-
public List<ValidationResult> InvalidMemberNames() => _invalidMemberNames;
171+
public List<ValidationResult> GetInvalidMemberNames() => _invalidMemberNames;
172172
}
173173

174174
class ComplexFoo : Foo

src/BootstrapBlazor.Server/Data/CustomValidateCollectionModel.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ public IEnumerable<ValidationResult> Validate(ValidationContext validationContex
6161
/// <inheritdoc/>
6262
/// </summary>
6363
/// <returns></returns>
64-
public List<string> ValidMemberNames() => _validMemberNames;
64+
public List<string> GetValidMemberNames() => _validMemberNames;
6565

6666
/// <summary>
6767
/// <inheritdoc/>
6868
/// </summary>
6969
/// <returns></returns>
70-
public List<ValidationResult> InvalidMemberNames() => _invalidMemberNames;
70+
public List<ValidationResult> GetInvalidMemberNames() => _invalidMemberNames;
7171
}

src/BootstrapBlazor/BootstrapBlazor.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
4-
<Version>8.9.4-beta05</Version>
4+
<Version>8.9.4-beta06</Version>
55
</PropertyGroup>
66

77
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">

src/BootstrapBlazor/Components/Validate/IValidateCollection.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ public interface IValidateCollection
2020
/// 返回合法成员集合
2121
/// </summary>
2222
/// <returns></returns>
23-
List<string> ValidMemberNames();
23+
List<string> GetValidMemberNames();
2424

2525
/// <summary>
2626
/// 返回非法成员集合
2727
/// </summary>
2828
/// <returns></returns>
29-
List<ValidationResult> InvalidMemberNames();
29+
List<ValidationResult> GetInvalidMemberNames();
3030
}

src/BootstrapBlazor/Components/Validate/ValidateBase.cs

+1
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ protected override void OnParametersSet()
301301

302302
if (ValidateForm != null)
303303
{
304+
// IValidateCollection 支持组件间联动验证
304305
var fieldName = FieldIdentifier?.FieldName;
305306
if (!string.IsNullOrEmpty(fieldName))
306307
{

src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs

+50-38
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,13 @@ public partial class ValidateForm
2121
/// <see cref="EditContext"/> is determined to be valid.
2222
/// </summary>
2323
[Parameter]
24-
[NotNull]
2524
public Func<EditContext, Task>? OnValidSubmit { get; set; }
2625

2726
/// <summary>
2827
/// A callback that will be invoked when the form is submitted and the
2928
/// <see cref="EditContext"/> is determined to be invalid.
3029
/// </summary>
3130
[Parameter]
32-
[NotNull]
3331
public Func<EditContext, Task>? OnInvalidSubmit { get; set; }
3432

3533
/// <summary>
@@ -107,6 +105,11 @@ public partial class ValidateForm
107105
/// </summary>
108106
private readonly ConcurrentDictionary<(string FieldName, Type ModelType), (FieldIdentifier FieldIdentifier, IValidateComponent ValidateComponent)> _validatorCache = new();
109107

108+
/// <summary>
109+
/// 验证组件验证结果缓存
110+
/// </summary>
111+
private readonly ConcurrentDictionary<IValidateComponent, List<ValidationResult>> _validateResults = new();
112+
110113
private string? DisableAutoSubmitString => (DisableAutoSubmitFormByEnter.HasValue && DisableAutoSubmitFormByEnter.Value) ? "true" : null;
111114

112115
/// <summary>
@@ -165,7 +168,7 @@ internal void AddValidator((string FieldName, Type ModelType) key, (FieldIdentif
165168
/// </summary>
166169
/// <param name="key"></param>
167170
/// <param name="value"></param>
168-
internal bool TryRemoveValidator((string FieldName, Type ModelType) key, [MaybeNullWhen(false)] out (FieldIdentifier FieldIdentifier, IValidateComponent IValidateComponent) value) => _validatorCache.TryRemove(key, out value);
171+
internal bool TryRemoveValidator((string FieldName, Type ModelType) key, out (FieldIdentifier FieldIdentifier, IValidateComponent IValidateComponent) value) => _validatorCache.TryRemove(key, out value);
169172

170173
/// <summary>
171174
/// 设置指定字段错误信息
@@ -186,19 +189,21 @@ public void SetError<TModel>(Expression<Func<TModel, object?>> expression, strin
186189

187190
private void InternalSetError(MemberExpression exp, string errorMessage)
188191
{
189-
var fieldName = exp.Member.Name;
190192
if (exp.Expression != null)
191193
{
194+
var fieldName = exp.Member.Name;
192195
var modelType = exp.Expression.Type;
193196
var validator = _validatorCache.FirstOrDefault(c => c.Key.ModelType == modelType && c.Key.FieldName == fieldName).Value.ValidateComponent;
194-
if (validator != null)
197+
if (validator == null)
195198
{
196-
var results = new List<ValidationResult>
197-
{
198-
new(errorMessage, new string[] { fieldName })
199-
};
200-
validator.ToggleMessage(results);
199+
return;
201200
}
201+
202+
var results = new List<ValidationResult>
203+
{
204+
new(errorMessage, [fieldName])
205+
};
206+
validator.ToggleMessage(results);
202207
}
203208
}
204209

@@ -213,7 +218,7 @@ public void SetError(string propertyName, string errorMessage)
213218
{
214219
var results = new List<ValidationResult>
215220
{
216-
new(errorMessage, new string[] { fieldName })
221+
new(errorMessage, [fieldName])
217222
};
218223
validator.ToggleMessage(results);
219224
}
@@ -241,7 +246,7 @@ private bool TryGetModelField(string propertyName, [MaybeNullWhen(false)] out Ty
241246
return propNames.IsEmpty;
242247
}
243248

244-
private bool TryGetValidator(Type modelType, string fieldName, [NotNullWhen(true)] out IValidateComponent validator)
249+
private bool TryGetValidator(Type modelType, string fieldName, out IValidateComponent validator)
245250
{
246251
validator = _validatorCache.FirstOrDefault(c => c.Key.ModelType == modelType && c.Key.FieldName == fieldName).Value.ValidateComponent;
247252
return validator != null;
@@ -256,6 +261,8 @@ private bool TryGetValidator(Type modelType, string fieldName, [NotNullWhen(true
256261
/// <param name="results"></param>
257262
internal async Task ValidateObject(ValidationContext context, List<ValidationResult> results)
258263
{
264+
_validateResults.Clear();
265+
259266
if (ValidateAllProperties)
260267
{
261268
await ValidateProperty(context, results);
@@ -266,30 +273,29 @@ internal async Task ValidateObject(ValidationContext context, List<ValidationRes
266273
foreach (var key in _validatorCache.Keys)
267274
{
268275
// 验证 DataAnnotations
269-
var validatorValue = _validatorCache[key];
270-
var validator = validatorValue.ValidateComponent;
271-
var fieldIdentifier = validatorValue.FieldIdentifier;
272-
if (validator.IsNeedValidate)
276+
var (fieldIdentifier, validator) = _validatorCache[key];
277+
if (!validator.IsNeedValidate)
273278
{
274-
var messages = new List<ValidationResult>();
275-
var pi = key.ModelType.GetPropertyByName(key.FieldName);
276-
if (pi != null)
279+
continue;
280+
}
281+
282+
var messages = new List<ValidationResult>();
283+
var pi = key.ModelType.GetPropertyByName(key.FieldName);
284+
if (pi != null)
285+
{
286+
var propertyValidateContext = new ValidationContext(fieldIdentifier.Model, context, null)
277287
{
278-
var propertyValidateContext = new ValidationContext(fieldIdentifier.Model, context, null)
279-
{
280-
MemberName = fieldIdentifier.FieldName,
281-
DisplayName = fieldIdentifier.GetDisplayName()
282-
};
288+
MemberName = fieldIdentifier.FieldName,
289+
DisplayName = fieldIdentifier.GetDisplayName()
290+
};
283291

284-
// 设置其关联属性字段
285-
var propertyValue = Utility.GetPropertyValue(fieldIdentifier.Model, fieldIdentifier.FieldName);
292+
// 设置其关联属性字段
293+
var propertyValue = Utility.GetPropertyValue(fieldIdentifier.Model, fieldIdentifier.FieldName);
286294

287-
await ValidateAsync(validator, propertyValidateContext, messages, pi, propertyValue);
288-
}
289-
// 客户端提示
290-
validator.ToggleMessage(messages);
291-
results.AddRange(messages);
295+
await ValidateAsync(validator, propertyValidateContext, messages, pi, propertyValue);
292296
}
297+
_validateResults.TryAdd(validator, messages);
298+
results.AddRange(messages);
293299
}
294300

295301
// 验证 IValidatableObject
@@ -306,22 +312,28 @@ internal async Task ValidateObject(ValidationContext context, List<ValidationRes
306312
}
307313
if (validate != null)
308314
{
309-
var messages = validate.Validate(context);
310-
if (messages.Any())
315+
var messages = validate.Validate(context).ToList();
316+
if (messages.Count > 0)
311317
{
312318
foreach (var key in _validatorCache.Keys)
313319
{
314320
var validatorValue = _validatorCache[key];
315321
var validator = validatorValue.ValidateComponent;
316322
if (validator.IsNeedValidate)
317323
{
318-
validator.ToggleMessage(messages);
324+
_validateResults[validator].AddRange(messages);
319325
}
320326
}
321327
results.AddRange(messages);
322328
}
323329
}
324330
}
331+
332+
ValidMemberNames.RemoveAll(name => _validateResults.Values.SelectMany(i => i).Any(i => i.MemberNames.Contains(name)));
333+
foreach (var (validator, messages) in _validateResults)
334+
{
335+
validator.ToggleMessage(messages);
336+
}
325337
}
326338
}
327339

@@ -425,7 +437,7 @@ private void ValidateDataAnnotations(object? value, ValidationContext context, L
425437
var errorMessage = !string.IsNullOrEmpty(rule.ErrorMessage) && rule.ErrorMessage.Contains("{0}")
426438
? rule.FormatErrorMessage(displayName)
427439
: rule.ErrorMessage;
428-
results.Add(new ValidationResult(errorMessage, new string[] { memberName }));
440+
results.Add(new ValidationResult(errorMessage, [memberName]));
429441
}
430442
}
431443
}
@@ -507,7 +519,7 @@ private async Task ValidateAsync(IValidateComponent validator, ValidationContext
507519
if (messages.Count == 0)
508520
{
509521
// 自定义验证组件
510-
_tcs = new();
522+
_tcs = new TaskCompletionSource<bool>();
511523
await validator.ValidatePropertyAsync(propertyValue, context, messages);
512524
_tcs.TrySetResult(messages.Count == 0);
513525
}
@@ -527,8 +539,8 @@ private async Task ValidateAsync(IValidateComponent validator, ValidationContext
527539
if (validate != null)
528540
{
529541
messages.AddRange(validate.Validate(context));
530-
ValidMemberNames.AddRange(validate.ValidMemberNames());
531-
InvalidMemberNames.AddRange(validate.InvalidMemberNames());
542+
ValidMemberNames.AddRange(validate.GetValidMemberNames());
543+
InvalidMemberNames.AddRange(validate.GetInvalidMemberNames());
532544
}
533545
}
534546
}

0 commit comments

Comments
 (0)