Skip to content

Commit 0437ee4

Browse files
committed
Added ObservableValidator.ValidateAllProperties() API
1 parent 02843c6 commit 0437ee4

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.ComponentModel.DataAnnotations;
1010
using System.Diagnostics.Contracts;
1111
using System.Linq;
12+
using System.Reflection;
1213
using System.Runtime.CompilerServices;
1314

1415
namespace Microsoft.Toolkit.Mvvm.ComponentModel
@@ -382,6 +383,30 @@ IEnumerable<ValidationResult> GetAllErrors()
382383
[Pure]
383384
IEnumerable INotifyDataErrorInfo.GetErrors(string? propertyName) => GetErrors(propertyName);
384385

386+
/// <summary>
387+
/// Validates all the properties in the current instance and updates all the tracked errors.
388+
/// If any changes are detected, the <see cref="ErrorsChanged"/> event will be raised.
389+
/// </summary>
390+
/// <remarks>
391+
/// Only public instance properties (excluding custom indexers) that have at least one
392+
/// <see cref="ValidationAttribute"/> applied to them will be validated. All other
393+
/// members in the current instance will be ignored. None of the processed properties
394+
/// will be modified - they will only be used to retrieve their values and validate them.
395+
/// </remarks>
396+
protected void ValidateAllProperties()
397+
{
398+
foreach (PropertyInfo propertyInfo in
399+
GetType()
400+
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
401+
.Where(static p => p.GetIndexParameters().Length == 0 &&
402+
p.GetCustomAttributes<ValidationAttribute>(true).Any()))
403+
{
404+
object? propertyValue = propertyInfo.GetValue(this);
405+
406+
ValidateProperty(propertyValue, propertyInfo.Name);
407+
}
408+
}
409+
385410
/// <summary>
386411
/// Validates a property with a specified name and a given input value.
387412
/// If any changes are detected, the <see cref="ErrorsChanged"/> event will be raised.

UnitTests/UnitTests.Shared/Mvvm/Test_ObservableValidator.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,47 @@ public void Test_ObservableValidator_ClearErrors()
298298
Assert.IsTrue(events[1].PropertyName == nameof(Person.Name));
299299
}
300300

301+
[TestCategory("Mvvm")]
302+
[TestMethod]
303+
public void Test_ObservableValidator_ValidateAllProperties()
304+
{
305+
var model = new PersonWithDeferredValidation();
306+
var events = new List<DataErrorsChangedEventArgs>();
307+
308+
model.ErrorsChanged += (s, e) => events.Add(e);
309+
310+
model.ValidateAllProperties();
311+
312+
Assert.IsTrue(model.HasErrors);
313+
Assert.IsTrue(events.Count == 2);
314+
315+
// Note: we can't use an index here because the order used to return properties
316+
// from reflection APIs is an implementation detail and might change at any time.
317+
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Name)));
318+
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Age)));
319+
320+
events.Clear();
321+
322+
model.Name = "James";
323+
model.Age = 42;
324+
325+
model.ValidateAllProperties();
326+
327+
Assert.IsFalse(model.HasErrors);
328+
Assert.IsTrue(events.Count == 2);
329+
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Name)));
330+
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Age)));
331+
332+
events.Clear();
333+
334+
model.Age = -10;
335+
336+
model.ValidateAllProperties();
337+
Assert.IsTrue(model.HasErrors);
338+
Assert.IsTrue(events.Count == 1);
339+
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Age)));
340+
}
341+
301342
public class Person : ObservableValidator
302343
{
303344
private string name;
@@ -331,6 +372,25 @@ public int Age
331372
}
332373
}
333374

375+
public class PersonWithDeferredValidation : ObservableValidator
376+
{
377+
[MinLength(4)]
378+
[MaxLength(20)]
379+
[Required]
380+
public string Name { get; set; }
381+
382+
[Range(18, 100)]
383+
public int Age { get; set; }
384+
385+
// Extra property with no validation
386+
public float Foo { get; set; } = float.NaN;
387+
388+
public new void ValidateAllProperties()
389+
{
390+
base.ValidateAllProperties();
391+
}
392+
}
393+
334394
/// <summary>
335395
/// Test model for linked properties, to test <see cref="ObservableValidator.ValidateProperty(object?, string?)"/> instance.
336396
/// See https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/3665 for the original request for this feature.

0 commit comments

Comments
 (0)