Reputation: 2376
In my API project I have multiple endpoints for simple CRUD operations. The endpoints use FluentValidation to validate the supplied models by adding the Validators to the DI and using ModelState.IsValid
.
The ModelState.IsValid works fine when there is just one model as parameter, but as soon I have multiple models then only the first get's validated, the following example makes this issue a bit claerer:
My UPDATE
endpoint operation takes 2 parameters (Id and model):
public async Task<IActionResult> UpdateMedium([CustomizeValidator(RuleSet = ValidationClass.UPDATE)] [FromRoute] long id, [FromBody] Medium model)
{
if (!this.ModelState.IsValid)
{
return this.BadRequest(this.ModelState);
}
if (id != model.MediumID)
{
return this.BadRequest();
}
// Etc....
}
The this.ModelState.IsValid
now only validates the first model (Id) and the second model get's ignored.
What would be the best approach of validating both models?
The Id parameter is obviously quite simple but I made a FluentValidation validator for it anyway (in case we want to add more validating logic to it in the future):
public class IdValidator : AbstractValidator<long>
{
public IdValidator()
{
RuleFor(r => r).NotEmpty().WithMessage("Id is empty.");
}
}
Additionally, the Medium model validator looks as follow:
public class MediumValidator : AbstractValidator<Medium>
{
public MediumValidator()
{
RuleSet(ValidationClass.CREATE, Create);
RuleSet(ValidationClass.UPDATE, Update);
}
private void Create()
{
RuleFor(r => r.InsertDateTime).Empty().WithMessage("{PropertyName} is not empty.");
RuleFor(r => r.InsertUserID).Empty().WithMessage("{PropertyName} is not empty.");
RuleFor(r => r.SolutionID).Empty().WithMessage("{PropertyName} is not empty.");
RuleFor(r => r.Name).NotEmpty().WithMessage("{PropertyName} is not filled in.");
RuleFor(r => r.Name).MaximumLength(100).WithMessage("The length of {PropertyName} must be {MaxLength} characters or fewer. You entered {TotalLength} characters.");
}
private void Update()
{
RuleFor(r => r.UpdateDateTime).Empty().WithMessage("{PropertyName} is not empty.");
RuleFor(r => r.UpdateUserID).Empty().WithMessage("{PropertyName} is not empty.");
RuleFor(r => r.MediumID).NotEmpty().WithMessage("{PropertyName} is empty.");
RuleFor(r => r.Name).NotEmpty().WithMessage("{PropertyName} is not filled in.");
RuleFor(r => r.Name).MaximumLength(100).WithMessage("The length of {PropertyName} must be {MaxLength} characters or fewer. You entered {TotalLength} characters.");
}
}
Perhaps I should only use the model as parameter and then just make the ID field required?
UPDATE
The setup of the FluentValidation in the startup:
services.AddMvc()
.AddFluentValidation(fv =>
{
fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
});
// Add fluent validators
services.AddTransient<IValidator<long>, IdValidator>();
services.AddTransient<IValidator<Medium>, MediumValidator>();
UPDATE2:
I am using swagger to test the endpoint (JSON input).
Upvotes: 1
Views: 2524
Reputation: 2376
For now I have implemented a different approach. For the UPDATE operation I now manually add the Medium model errors to the ModelState
:
var validator = new MediumValidator();
var result = validator.Validate(model, ruleSet: ValidationClass.UPDATE);
result.AddToModelState(this.ModelState, null);
if (!this.ModelState.IsValid)
{
return this.BadRequest(this.ModelState);
}
Better solutions are always welcome!
Upvotes: 1