Validation
FastEndpoints comes with FluentValidation built in.
FluentValidation has beautiful syntax with error messages and most common validation taken care of.
Libraries & Tools
Example & Thoughts
I throw my Validator classes right in there with the endpoint, the request and the response within one file. Thats the only time it actually gets used after all.
One important thing to understand when you want to keep validation simple is that there are TWO different kinds of validation.
- Request validation - Simple validation that can be statically done. Not empty, a certain length, must contain XY.
- Application logic validation - Basically business logic. More complicated. Can be totally dynamic.
You basically want to use FluentValidation only for request validation.
The moment you start injecting services and configs into your Validator, you lost.
Keep that logic in the endpoint.
public class CreateAddressEndpoint : Endpoint<CreateAddressRequest>
{
public override async Task<CreateAddressResponse> ExecuteAsync(CreateAddressRequest req, CancellationToken ct)
{
// Application validation
if(!externalService.IsPostCodeWithinCity(req.PostCode, req.City))
AddError(new ValidationFailure(nameof(CreateAddressRequest.PostCode), "Postal code doesnt exist in City"));
}
}
public class CreateAddressRequest
{
public string PostCode { get; set; }
public string City { get; set; }
}
public class CreateAddressValidator : Validator<CreateAddressRequest>
{
public CreateAddressValidator()
{
// Request validation
RuleFor(x => x.PostCode).Length(5);
}
}
Ensuring that a postal code has a length of exactly 5 is request validation. (I know, not always, not all countries etc. But just pretend.)
Ensuring that the postal code actually exists within that city is application logic.
If you try to have all your "validation" in one place, things will get akward.
Because "validation" is actually 2 different things.