Skip to content

Commit 3a2a956

Browse files
committed
Changes per PR
1 parent 55c5ec3 commit 3a2a956

13 files changed

+166
-32
lines changed

src/Components/Blazor/Validation/src/BlazorCompareAttribute.cs renamed to src/Components/Blazor/Validation/src/ComparePropertyAttribute.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ namespace System.ComponentModel.DataAnnotations
66
/// <summary>
77
/// A <see cref="ValidationAttribute"/> that compares two properties
88
/// </summary>
9-
public sealed class BlazorCompareAttribute : CompareAttribute
9+
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
10+
public sealed class ComparePropertyAttribute : CompareAttribute
1011
{
1112
/// <summary>
1213
/// Initializes a new instance of <see cref="BlazorCompareAttribute"/>.
1314
/// </summary>
1415
/// <param name="otherProperty">The property to compare with the current property.</param>
15-
public BlazorCompareAttribute(string otherProperty)
16+
public ComparePropertyAttribute(string otherProperty)
1617
: base(otherProperty)
1718
{
1819
}

src/Components/Blazor/Validation/src/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
5-
<Description>Provides experimental support for validation of complex properties using DataAnnotations.</Description>
5+
<Description>Provides experimental support for validation using DataAnnotations.</Description>
66
<IsShippingPackage>true</IsShippingPackage>
77
</PropertyGroup>
88

src/Components/Blazor/Validation/src/BlazorDataAnnotationsValidator.cs renamed to src/Components/Blazor/Validation/src/ObjectGraphDataAnnotationsValidator.cs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77

88
namespace Microsoft.AspNetCore.Components.Forms
99
{
10-
public class BlazorDataAnnotationsValidator : ComponentBase
10+
public class ObjectGraphDataAnnotationsValidator : ComponentBase
1111
{
1212
private static readonly object ValidationContextValidatorKey = new object();
13+
private static readonly object ValidatedObjectsKey = new object();
1314
private ValidationMessageStore _validationMessageStore;
1415

1516
[CascadingParameter]
@@ -23,7 +24,7 @@ protected override void OnInitialized()
2324
EditContext.OnValidationRequested += (sender, eventArgs) =>
2425
{
2526
_validationMessageStore.Clear();
26-
ValidateObject(EditContext.Model);
27+
ValidateObject(EditContext.Model, new HashSet<object>());
2728
EditContext.NotifyValidationStateChanged();
2829
};
2930

@@ -32,27 +33,33 @@ protected override void OnInitialized()
3233
ValidateField(EditContext, _validationMessageStore, eventArgs.FieldIdentifier);
3334
}
3435

35-
internal void ValidateObject(object value)
36+
internal void ValidateObject(object value, HashSet<object> visited)
3637
{
3738
if (value is null)
3839
{
3940
return;
4041
}
4142

43+
if (!visited.Add(value))
44+
{
45+
// Already visited this object.
46+
return;
47+
}
48+
4249
if (value is IEnumerable<object> enumerable)
4350
{
4451
var index = 0;
4552
foreach (var item in enumerable)
4653
{
47-
ValidateObject(item);
54+
ValidateObject(item, visited);
4855
index++;
4956
}
5057

5158
return;
5259
}
5360

5461
var validationResults = new List<ValidationResult>();
55-
ValidateObject(value, validationResults);
62+
ValidateObject(value, visited, validationResults);
5663

5764
// Transfer results to the ValidationMessageStore
5865
foreach (var validationResult in validationResults)
@@ -71,18 +78,20 @@ internal void ValidateObject(object value)
7178
}
7279
}
7380

74-
private void ValidateObject(object value, List<ValidationResult> validationResults)
81+
private void ValidateObject(object value, HashSet<object> visited, List<ValidationResult> validationResults)
7582
{
7683
var validationContext = new ValidationContext(value);
7784
validationContext.Items.Add(ValidationContextValidatorKey, this);
85+
validationContext.Items.Add(ValidatedObjectsKey, visited);
7886
Validator.TryValidateObject(value, validationContext, validationResults, validateAllProperties: true);
7987
}
8088

8189
internal static bool TryValidateRecursive(object value, ValidationContext validationContext)
8290
{
83-
if (validationContext.Items.TryGetValue(ValidationContextValidatorKey, out var result) && result is BlazorDataAnnotationsValidator validator)
91+
if (validationContext.Items.TryGetValue(ValidationContextValidatorKey, out var result) && result is ObjectGraphDataAnnotationsValidator validator)
8492
{
85-
validator.ValidateObject(value);
93+
var visited = (HashSet<object>)validationContext.Items[ValidatedObjectsKey];
94+
validator.ValidateObject(value, visited);
8695

8796
return true;
8897
}

src/Components/Blazor/Validation/src/ValidateComplexTypeAttribute.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ namespace System.ComponentModel.DataAnnotations
88
/// <summary>
99
/// A <see cref="ValidationAttribute"/> that indicates that the property is a complex or collection type that further needs to be validated.
1010
/// <para>
11-
/// By default <see cref="Validator"/> does not recurse in to complex property types during validation. When used in conjunction with <see cref="BlazorDataAnnotationsValidator"/>,
12-
/// this property allows the validation system to validate complex or collection type properties.
11+
/// By default <see cref="Validator"/> does not recurse in to complex property types during validation.
12+
/// When used in conjunction with <see cref="ObjectGraphDataAnnotationsValidator"/>, this property allows the validation system to validate
13+
/// complex or collection type properties.
1314
/// </para>
1415
/// </summary>
1516
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
@@ -18,9 +19,9 @@ public sealed class ValidateComplexTypeAttribute : ValidationAttribute
1819
/// <inheritdoc />
1920
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
2021
{
21-
if (!BlazorDataAnnotationsValidator.TryValidateRecursive(value, validationContext))
22+
if (!ObjectGraphDataAnnotationsValidator.TryValidateRecursive(value, validationContext))
2223
{
23-
throw new InvalidOperationException($"{nameof(ValidateComplexTypeAttribute)} can only used with {nameof(BlazorDataAnnotationsValidator)}.");
24+
throw new InvalidOperationException($"{nameof(ValidateComplexTypeAttribute)} can only used with {nameof(ObjectGraphDataAnnotationsValidator)}.");
2425
}
2526

2627
return ValidationResult.Success;

src/Components/Blazor/Validation/test/BlazorDatAnnotationsValidatorTest.cs renamed to src/Components/Blazor/Validation/test/ObjectGraphDataAnnotationsValidatorTest.cs

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
namespace Microsoft.AspNetCore.Components
1212
{
13-
public class BlazorDatAnnotationsValidatorTest
13+
public class ObjectGraphDataAnnotationsValidatorTest
1414
{
1515
public class SimpleModel
1616
{
@@ -306,6 +306,125 @@ public void ValidateObject_ManyLevels()
306306
Assert.Equal(2, editContext.GetValidationMessages().Count());
307307
}
308308

309+
private class Person
310+
{
311+
[Required]
312+
public string Name { get; set; }
313+
314+
[ValidateComplexType]
315+
public Person Related { get; set; }
316+
}
317+
318+
[Fact]
319+
public void ValidateObject_RecursiveRelation()
320+
{
321+
var model = new Person { Related = new Person() };
322+
model.Related.Related = model;
323+
324+
var editContext = Validate(model);
325+
326+
var messages = editContext.GetValidationMessages(() => model.Name);
327+
Assert.Single(messages);
328+
329+
messages = editContext.GetValidationMessages(() => model.Related.Name);
330+
Assert.Single(messages);
331+
332+
Assert.Equal(2, editContext.GetValidationMessages().Count());
333+
}
334+
335+
[Fact]
336+
public void ValidateObject_RecursiveRelation_OverManySteps()
337+
{
338+
var person1 = new Person();
339+
var person2 = new Person { Name = "Valid name" };
340+
var person3 = new Person();
341+
var person4 = new Person();
342+
343+
person1.Related = person2;
344+
person2.Related = person3;
345+
person3.Related = person4;
346+
person4.Related = person1;
347+
348+
var editContext = Validate(person1);
349+
350+
var messages = editContext.GetValidationMessages(() => person1.Name);
351+
Assert.Single(messages);
352+
353+
messages = editContext.GetValidationMessages(() => person2.Name);
354+
Assert.Empty(messages);
355+
356+
messages = editContext.GetValidationMessages(() => person3.Name);
357+
Assert.Single(messages);
358+
359+
messages = editContext.GetValidationMessages(() => person4.Name);
360+
Assert.Single(messages);
361+
362+
Assert.Equal(3, editContext.GetValidationMessages().Count());
363+
}
364+
365+
private class Node
366+
{
367+
[Required]
368+
public string Id { get; set; }
369+
370+
[ValidateComplexType]
371+
public List<Node> Related { get; set; } = new List<Node>();
372+
}
373+
374+
[Fact]
375+
public void ValidateObject_RecursiveRelation_ViaCollection()
376+
{
377+
var node1 = new Node();
378+
var node2 = new Node { Id = "Valid Id" };
379+
var node3 = new Node();
380+
node1.Related.Add(node2);
381+
node2.Related.Add(node3);
382+
node3.Related.Add(node1);
383+
384+
var editContext = Validate(node1);
385+
386+
var messages = editContext.GetValidationMessages(() => node1.Id);
387+
Assert.Single(messages);
388+
389+
messages = editContext.GetValidationMessages(() => node2.Id);
390+
Assert.Empty(messages);
391+
392+
messages = editContext.GetValidationMessages(() => node3.Id);
393+
Assert.Single(messages);
394+
395+
Assert.Equal(2, editContext.GetValidationMessages().Count());
396+
}
397+
398+
[Fact]
399+
public void ValidateObject_RecursiveRelation_InCollection()
400+
{
401+
var person1 = new Person();
402+
var person2 = new Person { Name = "Valid name" };
403+
var person3 = new Person();
404+
var person4 = new Person();
405+
406+
person1.Related = person2;
407+
person2.Related = person3;
408+
person3.Related = person4;
409+
person4.Related = person1;
410+
411+
var editContext = Validate(person1);
412+
413+
var messages = editContext.GetValidationMessages(() => person1.Name);
414+
Assert.Single(messages);
415+
416+
messages = editContext.GetValidationMessages(() => person2.Name);
417+
Assert.Empty(messages);
418+
419+
messages = editContext.GetValidationMessages(() => person3.Name);
420+
Assert.Single(messages);
421+
422+
messages = editContext.GetValidationMessages(() => person4.Name);
423+
Assert.Single(messages);
424+
425+
Assert.Equal(3, editContext.GetValidationMessages().Count());
426+
}
427+
309428
[Fact]
310429
public void ValidateField_PropertyValid()
311430
{
@@ -394,7 +513,7 @@ public void ValidateField_ModelWithComplexProperty_AfterSubmitValidation()
394513
private static EditContext Validate(object model)
395514
{
396515
var editContext = new EditContext(model);
397-
var validator = new TestBlazorDataAnnotationsValidator { EditContext = editContext, };
516+
var validator = new TestObjectGraphDataAnnotationsValidator { EditContext = editContext, };
398517
validator.OnInitialized();
399518

400519
editContext.Validate();
@@ -405,15 +524,15 @@ private static EditContext Validate(object model)
405524
private static EditContext ValidateField(object model, in FieldIdentifier field)
406525
{
407526
var editContext = new EditContext(model);
408-
var validator = new TestBlazorDataAnnotationsValidator { EditContext = editContext, };
527+
var validator = new TestObjectGraphDataAnnotationsValidator { EditContext = editContext, };
409528
validator.OnInitialized();
410529

411530
editContext.NotifyFieldChanged(field);
412531

413532
return editContext;
414533
}
415534

416-
private class TestBlazorDataAnnotationsValidator : BlazorDataAnnotationsValidator
535+
private class TestObjectGraphDataAnnotationsValidator : ObjectGraphDataAnnotationsValidator
417536
{
418537
public new void OnInitialized() => base.OnInitialized();
419538
}

src/Components/Components.sln

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,8 +1627,8 @@ Global
16271627
{CD0EF85C-4187-4515-A355-E5A0D4485F40} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
16281628
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
16291629
{FD9BD646-9D50-42ED-A3E1-90558BA0C6B2} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
1630-
{B70F90C7-2696-4050-B24E-BF0308F4E059} = {FD9BD646-9D50-42ED-A3E1-90558BA0C6B2}
1631-
{A5617A9D-C71E-44DE-936C-27611EB40A02} = {FD9BD646-9D50-42ED-A3E1-90558BA0C6B2}
1630+
{B70F90C7-2696-4050-B24E-BF0308F4E059} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
1631+
{A5617A9D-C71E-44DE-936C-27611EB40A02} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
16321632
EndGlobalSection
16331633
GlobalSection(ExtensibilityGlobals) = postSolution
16341634
SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE}

src/Components/ComponentsNoDeps.slnf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
"Blazor\\DevServer\\src\\Microsoft.AspNetCore.Blazor.DevServer.csproj",
1414
"Blazor\\Http\\src\\Microsoft.AspNetCore.Blazor.HttpClient.csproj",
1515
"Blazor\\Http\\test\\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj",
16-
"Blazor\\Validation\\src\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj",
17-
"Blazor\\Validation\\test\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj",
1816
"Blazor\\Server\\src\\Microsoft.AspNetCore.Blazor.Server.csproj",
1917
"Blazor\\Templates\\src\\Microsoft.AspNetCore.Blazor.Templates.csproj",
18+
"Blazor\\Validation\\src\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj",
19+
"Blazor\\Validation\\test\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj",
2020
"Blazor\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj",
2121
"Blazor\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj",
2222
"Blazor\\testassets\\Microsoft.AspNetCore.Blazor.E2EPerformance\\Microsoft.AspNetCore.Blazor.E2EPerformance.csproj",

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/test/E2ETest/Tests/FormsTest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public async Task EditFormWorksWithDataAnnotationsValidator()
4646
var form = appElement.FindElement(By.TagName("form"));
4747
var userNameInput = appElement.FindElement(By.ClassName("user-name")).FindElement(By.TagName("input"));
4848
var acceptsTermsInput = appElement.FindElement(By.ClassName("accepts-terms")).FindElement(By.TagName("input"));
49-
var submitButton = appElement.FindElement(By.TagName("button"));
49+
var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]"));
5050
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
5151

5252
// The form emits unmatched attributes
@@ -302,7 +302,7 @@ public void CanWireUpINotifyPropertyChangedToEditContext()
302302
var appElement = Browser.MountTestComponent<NotifyPropertyChangedValidationComponent>();
303303
var userNameInput = appElement.FindElement(By.ClassName("user-name")).FindElement(By.TagName("input"));
304304
var acceptsTermsInput = appElement.FindElement(By.ClassName("accepts-terms")).FindElement(By.TagName("input"));
305-
var submitButton = appElement.FindElement(By.TagName("button"));
305+
var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]"));
306306
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
307307
var submissionStatus = appElement.FindElement(By.Id("submission-status"));
308308

@@ -340,7 +340,7 @@ public void ValidationMessageDisplaysMessagesForField()
340340
var emailContainer = appElement.FindElement(By.ClassName("email"));
341341
var emailInput = emailContainer.FindElement(By.TagName("input"));
342342
var emailMessagesAccessor = CreateValidationMessagesAccessor(emailContainer);
343-
var submitButton = appElement.FindElement(By.TagName("button"));
343+
var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]"));
344344

345345
// Doesn't show messages for other fields
346346
submitButton.Click();
@@ -371,7 +371,7 @@ public void ErrorsFromCompareAttribute()
371371
var confirmEmailValidationMessage = CreateValidationMessagesAccessor(confirmEmailContainer);
372372
var modelErrors = CreateValidationMessagesAccessor(appElement.FindElement(By.ClassName("model-errors")));
373373
CreateValidationMessagesAccessor(emailContainer);
374-
var submitButton = appElement.FindElement(By.TagName("button"));
374+
var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]"));
375375

376376
// Updates on edit
377377
emailInput.SendKeys("[email protected]\t");

src/Components/test/testassets/BasicTestApp/FormsTest/ExperimentalValidationComponent.razor

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
@using System.ComponentModel.DataAnnotations
22
@using Microsoft.AspNetCore.Components.Forms
33

4+
<p>
5+
This component is used to verify the use of the experimental ObjectGraphDataAnnotationsValidator type with IValidatableObject and deep validation, as well
6+
as the ComparePropertyAttribute.
7+
</p>
48

59
<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit">
6-
<BlazorDataAnnotationsValidator />
10+
<ObjectGraphDataAnnotationsValidator />
711

812
<p class="name">
913
Name: <InputText @bind-Value="model.Recipient" placeholder="Enter the recipient" />
@@ -104,7 +108,7 @@
104108
[EmailAddress(ErrorMessage = "Enter a valid email address")]
105109
public string EmailAddress { get; set; }
106110

107-
[BlazorCompare(nameof(EmailAddress))]
111+
[CompareProperty(nameof(EmailAddress))]
108112
[Display(Name = "Confirm email address")]
109113
public string ConfirmEmailAddress { get; set; }
110114

src/Components/test/testassets/BasicTestApp/FormsTest/SimpleValidationComponent.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<EditForm Model="@this" OnValidSubmit="@HandleValidSubmit" OnInvalidSubmit="@HandleInvalidSubmit" autocomplete="off">
55
@if (UseExperimentalValidator)
66
{
7-
<BlazorDataAnnotationsValidator />
7+
<ObjectGraphDataAnnotationsValidator />
88
}
99
else
1010
{

src/Components/test/testassets/BasicTestApp/FormsTest/TypicalValidationComponent.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
55
@if (UseExperimentalValidator)
66
{
7-
<BlazorDataAnnotationsValidator />
7+
<ObjectGraphDataAnnotationsValidator />
88
}
99
else
1010
{

0 commit comments

Comments
 (0)