Open webinar “Fluent Validation as a Data Validation Tool”





Hello again! As part of the launch of the C # Developer course , we conducted a traditional open lesson on the Fluent Validation tool . At the webinar, we examined how to get rid of a bunch of ifs using an example of checking the correctness of filling in customer data, studied the internal implementation of the library and how to put the Fluent Interface approach into practice. The webinar was conducted by Alexei Yagur , Team Lead at YouDo.






Why is validation necessary?



Wikipedia tells us that validation (from lat. Validus “healthy, strong, strong”) is proof that the requirements of a particular user, product, service or system are satisfied. As a rule, validation is carried out as necessary, assuming both an analysis of the specified conditions of use and an assessment of the conformity of the product characteristics to the existing requirements. The result of validation is the conclusion about the possibility of using the product for specific conditions.



As for the Fluent Validation tool, his knowledge will allow us to:





But this is all theory, let's better get to practice.



Case Study Validation: Interactive



So, the practical implementation of validation in C # is as follows:







We have a class Customer, which has the simplest set of fields: FirstName - name, LastName - last name, Age - age. And there is a certain CustomerManager class that saves, as we see, a new user (buyer) in the CustomerRepository and displays information to the console that the customer has been successfully added.



Let's try adding a custom and a manager who will manage the custom:



void Main() { var customer = new Customer { FirstName = " ", LastName = "", Age = 57, }; var manager = new CustomerManager(); manager.Add(customer); }
      
      





The result will be the output of the following text in the console:



.







As you can see, so far so good. But what happens if “spoiled” data suddenly starts appearing in our database. For example, if incorrect information is entered in the fields (phone number instead of name, age with a minus sign, etc.):



 { FirstName = "+79123456789", LastName = "valde@mar.ru", Age = -14, };
      
      





As a result, we will see that a custom meter with an incomprehensible data set will also be added:



+79123456789 valde@mar.ru .







Naturally, we do not want to have such data in our repository. How do we protect ourselves? The easiest option is to return an error if, for example, we do not have all the characters - letters. To do this, we set the condition for FirstName using if, and if the condition is not met, we stop the function using return and display the message “Error in name” on the console. We do the same with LastName. As for Age, then we do a range of numbers check, for example:



if (customer.Age < 14 || customer.Age > 180)







Now let's assume that we need to add additional fields for the buyer , for example, a phone. We will validate the phone using the condition that the entered values ​​must begin with "+79" and include only numbers. All this in itself will be a rather cumbersome design, but if we want to add more e-mail?



Anyway, after performing the above operations, we get a bunch of ifs and a large sheet of code . It will not be easy for an outside developer to understand such code. What to do?



Connecting Fluent Validation



LINQPad has the ability to connect the Fluent Validation library, which we do . In addition, we create another class CustomerValidator, which will be a validator. Accordingly, we prescribe all the necessary rules in it. We make additional adjustments, and remove the numerous ifs, because they are no longer needed.



As a result, our final code will look like this:



 void Main() { var customer = new Customer { FirstName = "Alex2", LastName = "Petrov1", Age = 10, Phone = "+791234567893", Email = "adsf@fadsf3.com" }; var manager = new CustomerManager(); manager.Add(customer); } class Customer { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public string Phone { get; set; } public string Email { get; set; } } class CustomerManager { CustomerRepository _repository; CustomerValidator _validator; public CustomerManager() { _repository = new CustomerRepository(); _validator = new CustomerValidator(); } public void Add(Customer customer) { if (!ValidateCustomer(customer)) { return; } _repository.Add(customer); Console.WriteLine($" {customer.FirstName} {customer.LastName}  ."); } private bool ValidateCustomer(Customer customer) { var result = _validator.Validate(customer); if (result.IsValid) { return true; } foreach(var error in result.Errors) { Console.WriteLine(error.ErrorMessage); } return false; } } class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator() { var msg = "   {PropertyName}:  {PropertyValue}"; RuleFor(c => c.FirstName) .Must(c => c.All(Char.IsLetter)).WithMessage(msg); RuleFor(c => c.LastName) .Must(c => c.All(Char.IsLetter)).WithMessage(msg); RuleFor(c => c.Age) .GreaterThan(14).WithMessage(msg) .LessThan(180).WithMessage(msg); RuleFor(c => c.Phone) .Must(IsPhoneValid).WithMessage(msg) .Length(12).WithMessage("    {MinLength}  {MaxLength}.  : {TotalLength}"); RuleFor(c => c.Email) .NotNull().WithMessage(msg) .EmailAddress(); } private bool IsPhoneValid(string phone) { return !(!phone.StartsWith("+79") || !phone.Substring(1).All(c => Char.IsDigit(c))); } } class CustomerRepository { Random _random; public CustomerRepository() { _random = new Random(); } public void Add(Customer customer) { var sleepInSeconds = _random.Next(2, 7); Thread.Sleep(1000 * sleepInSeconds); } }
      
      





And a little more theory



I would like to add a few words about Fluent Validation. This tool is called that way due to the "fluid" interface. Again, Wikipedia tells us that a fluid interface is a way to implement an object-oriented API, aimed at increasing the readability of the program source code. The definition, as we see, contains many beautiful and long words, which is not always clear. But we can say otherwise:

"A fluid interface is a way to implement an object-oriented API in which methods return the same interface on which they were called."

Alexey Yagur
As for the library itself, it includes the following components :



  1. The basic logic . Here is a link to GitHub where you can see the main logic.
  2. Auxiliary logic . FluentValidation.ValidatorAttribute is responsible for this logic.
  3. Context sensitive part . See FluentValidation.AspNetCore , FluentValidation.Mvc5 and FluentValidation.WebApi .
  4. Tests . Accordingly, we are interested in FluentValidation.Tests.AspNetCore , FluentValidation.Tests.Mvc5 , FluentValidation.Tests.WebApi and FluentValidation.Tests .


That's all, step by step steps in writing code, see the video . In addition, you may be interested in additional interactive on the topic "Variables in Error Texts" , which the teacher held near the end of the webinar.



See you at the C # Developer Course!



All Articles