Pass parameter to validator – fluent validation

I have a validator that I use for both an insert, and an update. One of the checks I do is to see if what is being inserted already exists. The code for the validator is:

public GrapeColourValidator(IGrapeRepository grapeRepository)
        {
            _grapeRepository = grapeRepository;

            RuleFor(x => x.Colour)
                .NotEmpty()
                .WithMessage("Colour is required")
                .MaximumLength(_maxLength)
                .WithMessage($"Colour cannot be more that {_maxLength} characters");

            RuleFor(x => x)
                .MustAsync(async (grapeColour, context, cancellation) =>
                {
                    return await GrapeColourExists(grapeColour.Colour).ConfigureAwait(false);
                })
                .WithMessage($"Grape colour already exists");
        }

        private async Task<bool> GrapeColourExists(string grapeColour)
        {
            var colourResult = await _grapeRepository.GetByColour(grapeColour).ConfigureAwait(false);
            return !colourResult.Any(x => x.Colour == grapeColour);
        }

The issue with this is that it runs for Update also, so the colour will definitely exists. What I want to do is pass a parameter, so I could do something like:

if(isInsert)
            {
                RuleFor(x => x)
                .MustAsync(async (grapeColour, context, cancellation) =>
                {
                    return await GrapeColourExists(grapeColour.Colour).ConfigureAwait(false);
                })
                .WithMessage($"Grape colour already exists");
            }

Is this possible?

Answer

I usually accomplish this with a property on the view model I am validating. Basically, you need a flag on the view model indicating whether it represents “new” or “existing” data.

If you have a numeric identifier (which includes Guid’s) just test for the default value of the Id:

// For int identifiers:
public class ViewModel
{
    public int Id { get; set; }

    public bool IsNew => Id == default(int);
}

// For GUID identifiers:
public class ViewModel
{
    public Guid Id { get; set; }

    public bool IsNew => Id == default(Guid );
}

And then add a When(...) clause to the validation rule:

RuleFor(x => x.Property)
    .Must(...)
    .When(x => x.IsNew);

The downside with testing a view model property is that it could be vulnerable to request tampering. Someone can POST a non default Guid or int in the request and make the validator think it is validating a persisted object.

Then again, you should be authenticating and authorizing every request, as well as checking anti-forgery tokens.