Use the built in extension method AddFluentValidation to resolve the rules you will write provided the inherit from AbstractValidators. This is probably using reflection to resolve from AbstractValidator. (This will resolve as Scoped) - read the docs for more info
You only need to inject IValidator<FooRequest> and call _requestValidator.ValidateAsync is you need perform async validation. Else the FooRequestValidator will be automagically called for you \ :D /
The magic from RegisterValidatorsFromAssemblyContaining will now resolve the validators for you. Use constructor injection to resolve concrete implementations at runtime.
Should request.SomeId be a default guid (the 000000 one) then the response would be a 400.
1 2 3 4 5 6 7 8 9 10 11
{ "type":"https://tools.ietf.org/html/rfc7231#section-6.5.1", "title":"One or more validation errors occurred.", "status":400, "traceId":"xxxx", "errors":{ "PartnerId":[ "SomeId must be set. Got 00000000-0000-0000-0000-000000000000." ] } }
Complex validations
You can inject services that do validations into the validator, it then pays to stop at that validation failure with .Cascade(CascadeMode.Stop). The services need to already be in the dependency injection container.
namespaceFooApi.Validators { publicclassFooRequestValidator : AbstractValidator<FooRequest> { publicFooRequestValidator(IMyService myservice1, IMyService myservice2) { RuleFor(x => x.SomeId) .Cascade(CascadeMode.Stop) .NotEmpty() .WithMessage((_, someId) => $"SomeId must be set. Got {someId}."); .MustAsync(async (_, someId, cancellationToken) => await myservice1.IsValid(someId, cancellationToken)) // here IsValid returns `Task<bool>` .WithMessage((_, someId) => $"Some data for {someId} could not be found.") .MustAsync(async (_, someId, cancellationToken) => { var someCollection = await myservice2.GetStuff(someId, cancellationToken); return !someCollection.Any(x => x.SomeProp); }) .WithMessage((_, someId) => $"Some other data for {someId} could not be found.") RuleFor(x => x.SomeString) .NotEmpty() .WithMessage((_, someString) => $"SomeString must be set. Got {someString}."); } ...
Unit tests
Testing is done with FluentValidation.TestHelper. Simply new up the validator and await the extension method TestValidateAsync and assert on ShouldHaveValidationErrorFor.
The .Should().BeFalse() assertions are done using Fluent Assertions.
publicclassFooRequestValidatorTests { [Fact] publicasync Task RuleForSomeId_GivenEmptyGuid_ShouldFailValidation() { // Arrange var validator = new FooRequestValidator(); var request = new FooRequest() { SomeId = Guid.Empty // this will be the default but this is more verbose };
// Act var result = await validator.TestValidateAsync(request);
// Assert result.IsValid.Should().BeFalse();
result .ShouldHaveValidationErrorFor(x => x.SomeId) .WithErrorMessage("SomeId must be set. Got 00000000-0000-0000-0000-000000000000."); } }