gandarez
gandarez

Reputation: 2662

Chainning Rules on Fluent Validation

I'm already using the latest version of Fluent Validation which is 5.4.0.0; What I need to achieve is:

Think on a Vehicle VIN, which depends upon Make Id, Model Id and Year of manufacture. Does in Fluent I have the ability to execute a Rule based on more rules when these rules are successfully executed?

I must validate a vehicle vin if all three rules (Make, Model and Year) passes.

I've also read the whole documentation and couldn't find any reference to implement it.

class VehicleValidator : AbstractValidator<Vehicle>
{
    public VehicleValidator()
    {             
        RuleFor(v => v.MakeId)                
            .NotEmpty()
            .WithMessage("Make Id is required") 

        RuleFor(v => v.ModelId) 
            .NotEmpty()
            .WithMessage("Model Id is required")    

        RuleFor(v => v.Year)
            .NotEqual(default(ushort))                
            .WithMessage("Year is required")    

        RuleFor(v => v)
            .MustBeAValidVehicle()
            .WithName("Vehicle")
            .Unless(v => v.IdMarca == null || v.IdModelo == null && v.Ano == default(short));

        RuleFor(v => v.VehicleVin)
            .NotEmpty()
            .WithMessage("Vehicle Vin is required")         

        //Only run this validation if the validation of MakeId, ModelId, Year and MustBeAValidVechile don't fail
        RuleFor(v => v.VehicleVin)
            .MustBeAValidVehicleVin()
            .Unless(v => v.Chassi == null || v.IdMarca == null || v.IdModelo == null || v.Ano == default(ushort));
    }
}

Upvotes: 0

Views: 3227

Answers (2)

gandarez
gandarez

Reputation: 2662

I found out a workaround for my problem. Simply created a class which holds a boolean property to determine whether is true or false.

Please refer to my post on FLuent Validation discussion board ValidationResultHolder - Rule Dependency

Upvotes: 1

bpruitt-goddard
bpruitt-goddard

Reputation: 3324

You can combine the list of VIN validation rules using CascadeMode.StopOnFirstFailure to ensure that you only see the first failure. This will prevent any additional processing.

public class VinValidator : AbstractValidator<Vin>
{
    public VinValidator()
    {
        RuleFor(x => x.Vin)
            .Cascade(CascadeMode.StopOnFirstFailure)
            .Must(HaveValidMake).WithMessage("Invalid Make")
            .Must(HaveValidModel).WithMessage("Invalid Model")
            .Must(HaveValidYear).WithMessage("Invalid Year")
            .Must(BeValidVin).WithMessage("Invalid VIN");
    }

    private bool HaveValidMake(string vin)
    {
        return vin.Contains("make");
    }

    private bool HaveValidModel(string vin)
    {
        return vin.Contains("model");
    }

    private bool HaveValidYear(string vin)
    {
        return vin.Contains("year");
    }

    private bool BeValidVin(string vin)
    {
        return vin.Length == 17;
    }
}

The following examples demonstrate the behavior:

"invalid" => "Invalid Make"
"xxxmakexxx" => "Invalid Model"
"xxxmakemodelxxx" => "Invalid Year"
"xxxmakemodelyearxxx" => "Invalid VIN"
"xxxmakemodelyear" => valid

Update to reflect example code:

If there are no other validations being preformed in the AbstractValidator, then you can set the class level property CascadeMode to StopOnFirstFailure to ensure that the validation stops on the first failure (see here). You can then keep the validation rules separate as you have in your example code.

Note: When testing, I noticed that the StopOnFirstFailure was not being respected for non-Must rules. You may need to convert your rules to Musts as I have above.

Upvotes: 2

Related Questions