Validation framework:
using FluentValidation;
namespace ASValidation.Validators.Common
{
public static class CommonValidators
{
// Validates address: max 200 chars, allowed chars only
public static IRuleBuilderOptions<T, string> ValidateAddress<T>(this IRuleBuilder<T, string> rule)
{
return rule
.MaximumLength(200).WithMessage("Address cannot exceed 200 characters.")
.Matches(@"^[a-zA-Z0-9\s\-,.#]+$").WithMessage("Address contains invalid characters.");
}
// Validates captcha: required and must be true
public static IRuleBuilderOptions<T, bool> ValidateCaptcha<T>(this IRuleBuilder<T, bool> rule)
{
return rule
.NotEmpty().WithMessage("Captcha is required.")
.Equal(true).WithMessage("Captcha must be verified.");
}
// Validates ID: required (not empty)
public static IRuleBuilderOptions<T, TId> ValidateId<T, TId>(this IRuleBuilder<T, TId> rule)
{
return rule
.NotEmpty()
.WithMessage("{PropertyName} is required.");
}
// Validates primary key ID: must not be default unless 0
public static IRuleBuilderOptions<T, int> ValidatePrimaryKeyID<T>(this IRuleBuilder<T, int> rule)
{
return rule
.Must(id => id == 0 || id != default)
.WithMessage("{PropertyName} is required.");
}
// Validates custom ID: 5-20 chars, alphanumeric, no spaces, case insensitive
public static IRuleBuilderOptions<T, string> ValidateCustomId<T>(this IRuleBuilder<T, string> rule)
{
return rule
.NotEmpty().WithMessage("ID is required.")
.MinimumLength(5).WithMessage("ID must be at least 5 characters long.")
.MaximumLength(20).WithMessage("ID must not exceed 20 characters.")
.Matches(@"^[a-zA-Z0-9]+$").WithMessage("ID must contain only alphanumeric characters and no spaces.")
.WithMessage("ID must be case insensitive.");
}
// Validates property is required (not empty)
public static IRuleBuilderOptions<T, TProperty> IsRequired<T, TProperty>(this IRuleBuilder<T, TProperty> rule)
{
return rule
.NotEmpty().WithMessage("{PropertyName} is required.");
}
// Validates page: must be greater than 0
public static IRuleBuilderOptions<T, int> ValidatePage<T>(this IRuleBuilder<T, int> rule)
{
return rule
.GreaterThan(0)
.WithMessage("Page must be greater than 0.");
}
}
}
Consuming validation framework rules in my API project:
using ASValidation.Validators.Common;
using ASValidation.Validators.Entity;
using FluentValidation;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace agentservice.Validators
{
public class SubmitAgencyDetailsRequestDTOValidator : AbstractValidator<SubmitAgencyDetailsRequestDTO>
{
public SubmitAgencyDetailsRequestDTOValidator()
{
RuleFor(x => x.AsAgencyId).ValidateId();
RuleFor(x => x.RegisteredEmailId).NotEmpty().ValidateEmail();
RuleFor(x => x.WebsiteURL).ValidateAgentWebsiteUrl().When(x => !string.IsNullOrWhiteSpace(x.WebsiteURL));
RuleFor(x => x.AccessMethods).NotEmpty().IsRequired();
RuleFor(x => x.TypeOfBusinessID).ValidateId();
}
}
}
To create a validation rule that only validates when a string property has a value (not null/empty). Currently, I'm using .When(x => !string.IsNullOrEmpty(x.Property)), but this is verbose and repetitive.
Current implementation:
RuleFor(x => x.WebsiteURL)
.ValidateAgentWebsiteUrl()
.When(x => !string.IsNullOrEmpty(x.WebsiteURL)); // Want to simplify this
Expected implementation
RuleFor(x => x.WebsiteURL)
.ValidateAgentWebsiteUrl()
.ValidateWhenNotEmpty(); // Custom extension method
Question
How can I create a ValidateWhenNotEmpty() extension method that:
- Skips validation for null/empty strings
- Works with any validator chain (e.g., EmailAddress(), Matches())
- Doesn't trigger "required" errors for empty fields
- A generic version that works for all data types
NotEmpty()before them.ValidRequiredXYZValidateWhenNotEmpty(): What would you expect this to do:RuleFor(x => x.SomeProp).ValidateWhenNotEmpty().IsRequired();?