Make Your Business Rules Cleaner With Fluent Validation

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
how do you add validation to your project a popular library for adding validation to any kind of project is called fluent validation and it also has support for running asynchronous validation so let's see how we can use it I'm going to implement the use case of creating a new customer and we want to implement the business rule that the email has to be unique so let's start from the application layer and create a feature folder for our customers and let's add a command for creating a new customer so I'm going to add a class which is going to be called create customer command this command should have all the information necessary to create a new customer and I'm going to use a record to represent my command I'm going to add the email and the name properties which match the properties that I have in my customer entity and I'm going going to implement the I request interface from mediator because that's what I'm using for handling my commands let's add the respective Handler for this class which is the create customer command Handler this is going to be a sealed class implementing the I request Handler interface and we're going to pass in the create customer command as the generic argument so inside of the handle method I want to create a new customer instance by invoking The Constructor so let's say I need to pass in the customer ID so I'm going to create a new customer ID instance and I need to pass in the email and the name of this customer now I don't have this Constructor in the customer entity so I'm going to create it and let's just adjust the names of these properties so this is going to be the name and we have the ID and the email and let's go ahead and assign these to the appropriate properties so we're assigning the name field to the name property so now if I go back to my command Handler the next step is going to be how do we add this customer to the database for that I have the I customer repository interface so let's go ahead and inject this from The Constructor and here I can say customer repository and I need a method to add this customer I don't have one yet so let's go ahead and Define it on our interface so this is going to be the method for adding a new customer to the repository and if I go to the implementation of this interface let's add the missing member to implement the add method I'm going to access my EF core database context and I need the customers database set to be able to call the add method to add the customer entity to the repository how are we going to process the changes from my command Handler well for that I have the unit of work pipeline Behavior because I'm using mediator and it's going to wrap my commands create a transaction scope so that all of my changes are persisted together and I'm just calling unit of work save changes to persist my changes when the transaction scope completes the business rule we want to enforce is that the email of the customer has to be unique across our domain so we can do a uniqueness check inside of our Command Handler and let's first implement this before I show you how you can use fluent validation so let's add a method inside of our customer repository that's going to return a Boolean value and I'm going to call it is email unique Asic and I'm going to pass it the email value so that it can perform this check let's implement this in the customer repository so this is going to be return and I'm going to actually make this asynchronous so how do we check if there's any customer with a given email we can access the customer's DB set and I can call the any async method to check if there is any customer with this given email of course I need to await this to return a Boolean so when is the email unique if any customer in the database exists with this email value this is going to return true so I actually want this to return false and this method should return true when this returns false so for this to be true pun intended I have to negate the result of any async so now I can go back to my command Handler and I can say if await customer repository is email unique and I can pass the email from my command of course I need to make this asynchronous so in the case that the email is not unique then I have broken this business rule and I should return some sort of error from my API a simple approach can be to throw an exception saying that the email is not unique but throwing generic exceptions like this isn't really helpful the better approach would be throwing a specific exception something like an email is not unique exception which is a specific exception and then you can handle it accordingly and let the color of your API know what went wrong so how do we take this and move it into a fluent validation validator so the first step is going to be to actually add a nuget package for fluent validation and I'm going to add the package with dependency injection support so that I can also register the validators from this assembly so I installed the fluent validation nougat package and it's a library for adding validation in your projects how do you add a validator while you create a class so I'm going to add a new class which is going to be the create customer command validator so this class let's say I make it public and sealed and it's going to implement the abstract validator base class and I need to specify what is the object that I'm validating and this is my create customer command now how I add validation rules is by calling the Constructor and I can then call the rule 4 method and specify what is the validation that I want to invoke on the create customer command I have access to the email and name properties so let's say I want to validate my email how do I actually add a uniqueness check so fluent validation has support for asynchronous validation and then I can do something like this I can define an asynchronous delegate which accepts an email argument and a cancellation token so if I don't need the cancellation token I can just use another score to discard it and then I need to do my actual validation and return a true or false value so for the validation to fail I need to return a false value and if it is successful I need to return true so the really cool thing about fluent validation is that the validators are scoped meaning they are registered as scoped services and they also support dependency injection so I can inject my I customer repository interface and use it inside of the async validation so I can say return customer repository is email unique and pass it the email value of course I need to await this to satisfy my validator and we can also add an error message by calling with message and then we specify which message we want to return and let's say the email must be unique so this is how our asynchronous validator with fluent validation looks like and now I can remove this validation from my command Handler not make this asynchronous and let's just return task completed task from this handle method are we actually going to run our validation defined inside of the create customer command validator well first we need to register our validators with dependency injection so I already have the dependency injection class in my application layer and I need to call the add validators from assembly method which is exposed by the fluent validation library and I just need to pass it the assembly instance I'm going to pass it the application assembly reference and I'm going to take the instance from there and this is going to scan my application assembly and register all of my validators with dependency injection so now I can safely use them and I'm actually going to create an endpoint in my web API for creating a new customer so I don't have any customer endpoints yet so let's create them so this is going to be public and sealed and it's going to implement the icarner module because that's what I'm using to Define my endpoints so I need a post endpoint I'm going to give it the route of customers and I need to define a delegate representing my endpoint so let's make it asynchronous I need to bind to something on the request body which is going to match these properties that I have on my create customer command so let's create create customer request with the same property values and I'm doing this because I want to detach my command implementation details from the object that I'll be exposing as part of my public API so I'm going to use the create customer request and expose it in my endpoint I'm also going to need the I sender from mediator to be able to send this command and then in my endpoint I can say command is a new create customer command and we're going to pass in the email and the name I can send this command and return an OK result by saying results dot OK if everything succeeds now I also want to be able to run the validation to perform the email uniqueness check and I want to be using fluent validation for this so how you can do that is inject an i validator instance specify your create customer command as the generic argument which is going to match what you have defined on the abstract validator if we go to the validate real quick so the abstract validator under the hood implement the I validator interface of create customer command so this is what I'm using in my endpoint and now I can run my validation and it's super important that on the validator you invoke the validate async method you can also call the synchronous version just validate but this is going to throw an exception if you have asynchronous validations defined in your validator class so we're going to call validate async pass it the command instance so if we say if not result is valid then we have a problem our validation failed and we can return results and create a new validation problem instance and the really cool thing is you can map the validation result to a dictionary which you can pass to the validation problem method so this is how our endpoint looks like we're leveraging the I validator from the fluent validation library to run asynchronous validation checking if our email is unique if I send a post request to our endpoint with this request body and I made sure that this email already exists in the database let's see what is going to happen we first hit the breakpoint inside of our new endpoint so we're going to create our Command and then we're going to invoke our validator I'm going to press continue and we hit the breakpoint inside of the validator which is going to go into the repository and check with the database that the email is unique I'm going to press continue so we get back our validation result and is valid is set to false which means that we're going to return a validation problem from our API and if I press continue we can examine the response that we get in Postman you can see we are getting a 400 bad request response and in the errors property we have the email which is the one that failed validation with the message the email must be unique which matches the message we defined in our validator one thing worth noting here is that the call to the database in our customer repository is email unique method is going to be really performant because of the way that I configured my customer entity if I go to the customer configuration which is my fluent API configuration with ef core I have this call here which is going to Define an index on the email column in the database and it's going to make it a unique index what this means is that this query here that is checking if there is any email with this value is not going to be doing a table scan because there is a respective index and it's actually going to do an index seek which is going to be very performant and I also have the unique constraint in place at the database level making sure that nobody can bypass this business rule one more thing I want to mention is having to use an explicit I validator in all of your endpoints an alternative to this could be creating up validation pipeline Behavior with mediator and moving the validation inside of that and then finding a way to return a validation problem in case validation fails however I would still prefer to have this kind of validation be part of my command handlers because I think rules like this are part of your business logic and they belong in the command Handler which is going to make them explicit whereas if they were defined inside of the validators this is going to make them implicit and somebody has to actually know that the validator exists to be aware of the existence of this kind of business rule if you like this video about fluent validation I think you're going to enjoy this video next thanks for watching and until next time stay awesome
Info
Channel: Milan Jovanović
Views: 22,370
Rating: undefined out of 5
Keywords: fluent validation, fluent validation c# tutorial, fluent validation c#, fluent validation in asp.net core, fluent validation in asp.net core 6, fluent validation custom validator, fluent validation web api, fluent validation in asp.net core mvc, fluent validation async, fluent validation .net core, fluent validation .net 6, fluent validation ddd, fluent validation mediatr, fluent validation cqrs, fluent validation ivalidator, fluent validation problem details
Id: AYrmu9_RFnM
Channel Id: undefined
Length: 15min 14sec (914 seconds)
Published: Fri Jun 16 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.