In previous article, we have seen how the conditional validation logic can be added using fluent validation library. Real world applications may also need to connect to database for performing validations. Some of such validations include:
- Validating that the username is unique,
- or validating that the email is unique
- Validate if ID exists in the database, etc.
In this article, we are going to discuss about how to perform these kinds of validations.
Let’s say we want to validate if the username is unique. We are going to use the same API project and add the reference to FluentValidation.AspNetCore.
Once this is done, let’s add a controller, namely,
UserController. Let’s say there is an endpoint
RegisterUser, which takes a User type as input parameter. This user type has two properties,
Password. Then we will also create a validator.
The code snippet given below shows the
UserController and an
UserValidator. The validator contains only checks to ensure that both properties are not null.
For the sake of this demo, I will create a repository stub, which has a single method. Instead of connecting to the actual database, it just holds an in-memory list of users.
As mostly, database interactions are going to be asynchronous, I am going to create a method to lookup for user names which returns
Task<bool>. For making the method asynchronous, I just have added a
Task.Delay statement as shown in the code snippet. As you can see, the method just checks if the provided username exists in the list of three usernames.
Repository in the Validator
As a next step, we need to use the “repository” that we created in the previous section in the
UserValidator class. So, let’s inject this repository in the validator constructor. But now the question is – how to use it ?
In the validation rule for username, we can call a
MustAsync method. This method allows calling an asynchronous function. The asynchronous function called via
MustAsync must return a Boolean value. If the returned value is true, then the input is supposed to be valid. Otherwise input is supposed to be invalid.
We have a method in repository, which returns true if username exists. We can use that method. But when this method returns true and username already exists, it means that the username being validated is not unique. So
MustAsync should return false in that case.
The method that we use in
MustAsync will receive two parameters, first parameter is the value of the property which is being validated and second parameter is
The code snippet given below shows the complete implementation of the
UserValidator. Here, the repository is injected in the validator and then it is used to check if username is unique.
Validate vs ValidateAsync
If we just create a repository instance and pass it to the validator and then call
Validate method on the validator instance, we will get the exception shown in the snapshot given below.
Why did this happen ? It’s because when a validator contains asynchronous validation, the validation should always be triggered by using
ValidateAsync method. If we do not , then the above exception is thrown.
Modified Controller Version
So, let’s modify our code inside controller and use
ValidateAsync method to trigger the validation.
Now, let’s run and verify the API. If we pass any value from the existing usernames list, then the API would return us an error. Hence we have successfully written a validator which makes call to database and performs the validation based on the retrieved data.
Point to Note !
We need to consider few things while writing such validations which makes call to the database while performing validations. If validators are willing to fetch same data again and again, it would cause degradation of API’s performance. So, we may have to think of applying right design patterns to ensure that the data fetched once gets cached properly and it is used for next validation rule.
Actually this kind of validation logic works well with Entity Framework core as EF Core maintains its own cache of objects which are already fetched from the database.
I hope you find this information helpful. Let me know your thoughts.