FluentValidation is one of the
best validation libraries for .NET. I use it daily both at work
and in my personal pet projects. Still from time to time I
encounter situations where it is not obvious how
I should use FluentValidation.
In this blog post I describe one such situation that I have to
deal with recently.
In short I had to validate a simple DTO:
With a small twist that ContactInfo.PhoneNumber was
validated using country dependent format and information
about country itself was stored in Address.CountryIsoCode field.
This is generally a good use-case for FluentValidation Custom rule:
Unfortunately in my case I also had a bunch of other country dependent
values like VAT numbers scattered across many DTOs. And I needed
a more reusable and programmer friendly solution than Custom rule.
Ideally my validator definition should look like this:
Creating property validators like CountryIsoCode using FluentValidation
is very simple. You just extend PropertyValidator class,
provide an error message template to the base class ctor and override
Additionally you may define an extension method
to the IRuleBuilder<T,TProperty>
interface to make your validator behave like build-in ones.
CountryCode validator was easy, what about PhoneNumber validator?
Here the only challenge that we must solve
is finding a way to pass country ISO code from Address to
phone number validator.
To solve this problem I decided to use “advanced” FluentValidation
feature called “Root Context Data”. Basically this is a
IDictionary<string, object> that can be prefilled with custom data
before validation starts and then is accessible to every validator
in validators tree.
Looks very promising, and what’s better we can add values to RootContextData
straight inside top-level validators by overriding PreValidate method:
To avoid dealing with objects I have also created a strongly typed
wrapper (ValidationContextData class) around RootContextData
IMPORTANT: To make validators reusable you should set RootContextData only
in top level validators. Validators used with SetValidator
method are not considered top level.
Now implementing PhoneNumberValidator is easy:
And we are done!
FluentValidation provides several extension methods that
make unit-testing easy, just take a look:
Everything works right now, but there is still place for improvement.
For example what happens when a programmer forgets to
override PreValidate method and set all required properties?
Validation of certain properties will be silently skipped.
This is not good.
To minimize this problem I put additional checks inside ValidationContextData
class. They will throw an exception with a descriptive message if
validator tries to access a property that was not previously set.
In my application values like phone numbers are always validated against
country specific formats. But I can imaging situations where
sometimes we use country agnostic phone number validator and
we use country specific one. In such cases it would be good
to call the country agnostic validator just a PhoneNumberValidator and
the country specific validator a CountryDependentPhoneNumberValidator.
I have a mixed feelings about ValidationContextData class because
it is used by every country specific validator in my code. Maybe
instead of introducing this common dependency every validator should
access RootContextData and check if the property is set itself?