Search code examples
c#.netcqrsfluentvalidationmediatr

Fluent Validations - How to reuse rules for a property?


Iam using Fluent Validations with CQRS using MediatR library. The following Fluent package versions are used in my solution along with .NET 7 & C#.

FluentValidation - 11.8.1

FluentValidation.DependencyInjectionExtensions - 11.8.1

I have the following validators for "Group" property, a "string" datatype in CreateGroupRequest and UpdateGroupRequest.

public class CreateGroupRequest : IRequest<BaseResponse<CreateGroupResponse>>
{
    public string Group { get; set; }
}

public class CreateGroupValidator : AbstractValidator<CreateGroupRequest>
{
    public CreateGroupValidator(IOptions<FeatureOptions> options)
    {
        var featureOptions = options.Value;

        ClassLevelCascadeMode = CascadeMode.Stop;

        RuleFor(r => r.Group).NotNull().WithMessage("Group can't be null.");

        RuleFor(r => r.Group).NotEmpty().WithMessage("Group must not be empty.");

        RuleFor(r => r.Group).Length(featureOptions.GroupMinLength, featureOptions.GroupMaxLength)
                             .WithMessage("Group length must be between 1 and 15.");

        RuleFor(r => r.Group).Matches(featureOptions.GroupAllowedCharactersRegex)
                             .WithMessage("Group must start and end with lower case letters or numbers. It may contain \"_\" and \" - \".");
    }
}


public class UpdateGroupValidator : AbstractValidator<UpdateGroupRequest>
{
    public UpdateGroupValidator(IOptions<FeatureOptions> options)
    {
        var featureOptions = options.Value;

        ClassLevelCascadeMode = CascadeMode.Stop;

        RuleFor(r => r.Group).NotNull().WithMessage("Group can't be null.");

        RuleFor(r => r.Group).NotEmpty().WithMessage("Group must not be empty.");

        RuleFor(r => r.Group).Length(featureOptions.GroupMinLength, featureOptions .GroupMaxLength)
                             .WithMessage("Group length must be between 1 and 15.");

        RuleFor(r => r.Group).Matches(labelingOptions.GroupAllowedCharactersRegex)
                             .WithMessage("Group must start and end with lower case letters or numbers. It may contain \"_\" and \" - \".");
    }
}

I am using ClassLevelCascadeMode = CascadeMode.Stop setting in the validators.

There are other requests where "Group" property needs to be validated. Is there a way to move all the validations on the "Group" property so the rules are defined at a single place and reused in the validators?

Any help on this is appreciated.


Solution

  • First of all, you can simplify your code grouping all your rules in one declaration:

    public CreateGroupValidator(IOptions<FeatureOptions> options)
    {
        var featureOptions = options.Value;
    
        ClassLevelCascadeMode = CascadeMode.Stop;
    
        RuleFor(r => r.Group).NotNull().WithMessage("Group can't be null.")
                             .NotEmpty().WithMessage("Group must not be empty.")
                             .Length(featureOptions.GroupMinLength, featureOptions.GroupMaxLength).WithMessage("Group length must be between 1 and 15.")
                             .Matches(featureOptions.GroupAllowedCharactersRegex).WithMessage("Group must start and end with lower case letters or numbers. It may contain \"_\" and \" - \".");
    
    }
    

    Same thing for UpdateGroupValidator.


    That said, you can refactor using extension methods:

    public static class ValidationExtensions
    {
        public static IRuleBuilderOptions<TCommand, TProperty?> MustBeAValidGroup<TCommand, TProperty>(this IRuleBuilder<TCommand, TProperty?> myGroup)
        {
            return myGroup.NotNull().WithMessage("Group can't be null.")
                             .NotEmpty().WithMessage("Group must not be empty.")
                             .Length(featureOptions.GroupMinLength, featureOptions.GroupMaxLength).WithMessage("Group length must be between 1 and 15.")
                             .Matches(featureOptions.GroupAllowedCharactersRegex).WithMessage("Group must start and end with lower case letters or numbers. It may contain \"_\" and \" - \".");
        }
    }
    

    Then, adding the using of ValidationExtensions, you can use it in CreateGroupValidator and UpdateGroupValidator like:

    [...]
    RuleFor(r => r.Group).MustBeAValidGroup();
    [...]