I have the following :
internal sealed class LabelValidator : AbstractValidator<string>
{
public LabelValidator()
{
RuleFor(label => label)
.NotNull()
.NotEmpty()
.MaximumLength(120);
}
}
internal static class ValidatorExtensions
{
public static IRuleBuilderOptions<T, string> MustHaveValidLabel<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.SetValidator(new LabelValidator());
}
}
internal sealed class SharedFolderValidator : ResourceValidator<SharedFolderDto>
{
public SharedFolderValidator(string labelPrefix, string groupPrefixFullControl, string groupPrefixReadOnly)
{
RuleFor(resource => resource.Label)
.Cascade(CascadeMode.Stop)
.MustHaveValidLabel()
.Must(l => l.StartsWith(labelPrefix, StringComparison.Ordinal))
}
protected override bool PreValidate(ValidationContext<SharedFolderDto> context, ValidationResult result)
{
base.PreValidate(context, result);
[Some other tests]
return !result.Errors.Any();
}
}
public sealed class ValidatorBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
{
` private async Task<TResponse> Validate(TRequest request, RequestHandlerDelegate<TResponse> next)
{
List<ValidationFailure> failures = _validators
.Select(v => v.Validate(request))
.SelectMany(result => result.Errors)
.Where(error => error != null)
.ToList();
if (failures.Any())
{
_logger.LogValidationResult(typeName, JsonSerializer.Serialize(request), failures);
throw new ResourceDomainException();
}
return await next();
}
}
MustHaveValidLabel
is used on other usecases.
The code fails on .Must(l => l.StartsWith(labelPrefix, StringComparison.Ordinal))
when label
is empty / null but CascadeMode.Stop
should have had stopped it.
I'm not looking for workarounds.
I'd like to understand why CascadeMode.Stop
won't work in that usecase.
I added PipelineBehavior and static class for the extension.
According to my attempts to reproduce - your validator is not executed at all (not sure why though, maybe string
is a special type for fluent validation). One workaround is to change the validator to be a PropertyValidator<T, string>
:
internal sealed class LabelValidator<T> : PropertyValidator<T, string>
{
public LabelValidator()
{
}
public override bool IsValid(ValidationContext<T> context, string value)
{
return !string.IsNullOrEmpty(value) && value.Length <= 120;
}
protected override string GetDefaultMessageTemplate(string errorCode) => "label is not valid"; // TODO
public override string Name => "Label validator";
}
internal static class exts
{
public static IRuleBuilderOptions<T, string> MustHaveValidLabel<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.SetValidator(new LabelValidator<T>());
}
}
Or move the the rules to the extension method:
internal static class exts
{
public static IRuleBuilderOptions<T, string> MustHaveValidLabel<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.NotNull()
.NotEmpty()
.MaximumLength(120);
}
}