Reputation: 1038
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?
Upvotes: 2
Views: 5333
Reputation: 123
You could have 3 classes:
GrapeColourValidator: This class will contain validations needed for Create and Update
GrapeColourCreateValidator: This class inherits from GrapeColourValidator and contains only the validations for Creating the resource.
GrapeColourUpdateValidator: This class inherits from GrapeColourValidator and contains only the validations for Updating the resource.
And then in your Controller you can inject the 2 validators and use them when needed.
public class GrapesController : Controller
{
private readonly GrapeColourCreateValidator createValidator;
private readonly GrapeColourUpdateValidator updateValidator;
public GrapesController(GrapeColourCreateValidator createValidator, GrapeColourUpdateValidator updateValidator)
{
this.createValidator = createValidator;
this.updateValidator = updateValidator;
}
public async Task<IActionResult> Create(AddOrUpdateGrapeRequest request)
{
var results = await createValidator.ValidateAsync(request);
if (!results.IsValid)
{
results.AddToModelState(ModelState);
return BadRequest();
}
// save grape color
return // RedirectToAction() or Ok()
}
public async Task<IActionResult> Update(AddOrUpdateGrapeRequest request)
{
var results = await updateValidator.ValidateAsync(request);
if (!results.IsValid)
{
results.AddToModelState(ModelState);
return BadRequest();
}
// save grape color
return // RedirectToAction() or Ok()
}
}
Upvotes: 1
Reputation: 76
3 years too late, but for anyone wondering how to do this - you can use RootContextData https://docs.fluentvalidation.net/en/latest/advanced.html#root-context-data
var context = ValidationContext<yourTypeHere>(request);
context.RootContextData["IsUpdate"] = true;
validator.Validate(context);
and then in your validator you can get the IsUpdate value out of the context
context.RootContextData.TryGetValue("IsUpdate", out var IsUpdate)
Upvotes: 4
Reputation: 18928
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.
Upvotes: 2