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 @@ -104,12 +104,20 @@ public virtual async Task ValidateAsync(object? value, ValidateContext context,
{
if (validationResult != ValidationResult.Success && validationResult.ErrorMessage is not null)
{
var memberName = validationResult.MemberNames.First();
var key = string.IsNullOrEmpty(originalPrefix) ?
memberName :
$"{originalPrefix}.{memberName}";

context.AddOrExtendValidationError(key, validationResult.ErrorMessage);
// Create a validation error for each member name that is provided
foreach (var memberName in validationResult.MemberNames)
{
var key = string.IsNullOrEmpty(originalPrefix) ?
memberName :
$"{originalPrefix}.{memberName}";
context.AddOrExtendValidationError(key, validationResult.ErrorMessage);
}

if (!validationResult.MemberNames.Any())
{
// If no member names are specified, then treat this as a top-level error
context.AddOrExtendValidationError(string.Empty, validationResult.ErrorMessage);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,95 @@ public async Task Validate_RequiredOnPropertyShortCircuitsOtherValidations()
Assert.Equal("The Password field is required.", error.Value.Single());
}

[Fact]
public async Task Validate_IValidatableObject_WithZeroAndMultipleMemberNames_BehavesAsExpected()
{
var globalType = new TestValidatableTypeInfo(
typeof(GlobalErrorObject),
[]); // no properties – nothing sets MemberName

var context = new ValidateContext
{
ValidationOptions = new TestValidationOptions(new Dictionary<Type, ValidatableTypeInfo>
{
{ typeof(GlobalErrorObject), globalType }
})
};

var globalErrorInstance = new GlobalErrorObject { Data = -1 };
context.ValidationContext = new ValidationContext(globalErrorInstance);

await globalType.ValidateAsync(globalErrorInstance, context, default);

Assert.NotNull(context.ValidationErrors);
var globalError = Assert.Single(context.ValidationErrors);
Assert.Equal(string.Empty, globalError.Key);
Assert.Equal("Data must be positive.", globalError.Value.Single());

var multiType = new TestValidatableTypeInfo(
Copy link
Member

Choose a reason for hiding this comment

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

nit: should be a second test

Copy link
Member Author

Choose a reason for hiding this comment

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

Will address this in one of the next PRs I do in this area.

typeof(MultiMemberErrorObject),
[
CreatePropertyInfo(typeof(MultiMemberErrorObject), typeof(string), "FirstName", "FirstName", []),
CreatePropertyInfo(typeof(MultiMemberErrorObject), typeof(string), "LastName", "LastName", [])
]);

context.ValidationErrors = [];
context.ValidationOptions = new TestValidationOptions(new Dictionary<Type, ValidatableTypeInfo>
{
{ typeof(MultiMemberErrorObject), multiType }
});

var multiErrorInstance = new MultiMemberErrorObject { FirstName = "", LastName = "" };
context.ValidationContext = new ValidationContext(multiErrorInstance);

await multiType.ValidateAsync(multiErrorInstance, context, default);

Assert.NotNull(context.ValidationErrors);
Assert.Collection(context.ValidationErrors,
kvp =>
{
Assert.Equal("FirstName", kvp.Key);
Assert.Equal("FirstName and LastName are required.", kvp.Value.First());
},
kvp =>
{
Assert.Equal("LastName", kvp.Key);
Assert.Equal("FirstName and LastName are required.", kvp.Value.First());
});
}

// Returns no member names to validate https://github.com/dotnet/aspnetcore/issues/61739
private class GlobalErrorObject : IValidatableObject
{
public int Data { get; set; }

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Data <= 0)
{
yield return new ValidationResult("Data must be positive.");
}
}
}

// Returns multiple member names to validate https://github.com/dotnet/aspnetcore/issues/61739
private class MultiMemberErrorObject : IValidatableObject
{
public string? FirstName { get; set; }
public string? LastName { get; set; }

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrEmpty(FirstName) || string.IsNullOrEmpty(LastName))
{
// MULTIPLE member names
yield return new ValidationResult(
"FirstName and LastName are required.",
[nameof(FirstName), nameof(LastName)]);
}
}
}

private ValidatablePropertyInfo CreatePropertyInfo(
Type containingType,
Type propertyType,
Expand Down Expand Up @@ -534,7 +623,7 @@ public IEnumerable<ValidationResult> Validate(ValidationContext validationContex
{
if (Salary < 0)
{
yield return new ValidationResult("Salary must be a positive value.", new[] { nameof(Salary) });
yield return new ValidationResult("Salary must be a positive value.", ["Salary"]);
}
}
}
Expand Down
Loading