FluentValidation for .NET – SmaRF Tech Knowledge Base

Successful companies work actively to prevent knowledge silos within the organization. In fact, studies show that sharing your expertise improves creativity and problem solving skills, as well as overall team efficiency.

We talked a lot about the ways we could do this internally. However, when we made a list of all the technical expertise we could share, we thought it might be the best to share it on our blog.

That way, our junior colleagues could inform themselves about the industry, while potential clients could get a bigger picture of our tech stack.

In today’s article, we share an introduction to FluentValidation.

❓ What is FluentValidation?

FluentValidation is a small validation library for .NET that uses a fluent interface and lambda expressions for building validation rules. It belongs in the Exception Monitoring toolset of a tech stack.

As an open source tool with 7.7K GitHub stars and 1.1K GitHub Forks, it proves its versatility among the community. It’s quite popular because it’s simple and fast.

🔐 How to use FluentValidation within ASP.NET Core

We use FluentValidation within ASP.NET Core web applications to validate incoming models. When it comes to validation, there are two approaches:

Manual validation

With manual validation, you inject the validator into your controller (or API endpoint), invoke the validator and act upon the result. This is the most straightforward and reliable approach.

Automatic validation

With automatic validation, FluentValidation plugs into the validation pipeline that’s part of ASP.NET Core MVC and allows models to be validated before a controller action is invoked (during model-binding). 

Please note that the official documentation no longer recommends automatic validation for new projects.

Here’s how to create your first validator

To define a set of validation rules for an object, create a class that inherits from AbstractValidator <T>, where T is the type of class that you wish to validate.

In the code below, we’ll use a User object which is validated using a UserValidator. To define a set of validation rules for this class, you have to inherit from AbstractValidator<User>. 

public class User
{
  public int Id { get; set; }
  public string Name { get; set; }
  public string Email { get; set; }
  public int Age { get; set; }
}

public class UserValidator : AbstractValidator<User> 
{
  public UserValidator() 
  {
    RuleFor(x => x.Id).NotNull();
    RuleFor(x => x.Name).Length(0, 10);
    RuleFor(x => x.Email).EmailAddress();
    RuleFor(x => x.Age).InclusiveBetween(18, 60);
  }
}

Manual Validation with FluentValidation

With the manual validation approach, you’ll inject the validator into your controller (or Razor page) and invoke it against the model.

For example, you might have a controller that looks like this:

public class UserController : Controller 
{
  private IValidator<User> _validator;
  private IUserRepository _repository;

  public UserController(IValidator<User> validator, IUserRepository repository) 
  {
    // Inject the validator and also a DB context for storing the user object.
    _validator = validator;
    _repository = repository;
  }

  public ActionResult Create() 
  {
    return View();
  }

  [HttpPost]
  public async Task<IActionResult> Create(User user) 
  {
    ValidationResult result = await _validator.ValidateAsync(user);

    if (!result.IsValid) 
    {
      // Copy the validation results into ModelState.
      // ASP.NET uses the ModelState collection to populate 
      // error messages in the View.
      result.AddToModelState(this.ModelState);

      // re-render the view when validation failed.
      return View("Create", user);
    }

    _repository.Save(user); //Save the user to the database, or some other logic

    TempData["notice"] = "User successfully created";
    return RedirectToAction("Index");
  }
}

Dependent Rules in FluentValidation

By default, rules in FluentValidation are separate and cannot influence one another. 

This is intentional and necessary for asynchronous validation to work.

However, there may be some cases where you want some rules to execute only after another one has been completed. You can use DependentRules to do this.

To use dependent rules, call the DependentRules method at the end of the rule that you want others to depend on. This method accepts a lambda expression inside which you can define other rules that will be executed only if the first rule passes:

RuleFor(x => x.Surname).NotNull().DependentRules(() => {
  RuleFor(x => x.Forename).NotNull();
});

Here the rule against Forename will only be run if the Surname rule passes.

Overriding the default error message

Overriding the default error message for a validator is possible by calling the WithMessage method on a validator definition:

RuleFor(user => user.Surname).NotNull().WithMessage("Please ensure that you have entered your Surname");

Note that custom error messages can contain placeholders for special values such as {PropertyName} – which will be replaced in this example with the name of the property being validated. 

This means the above error message could be rewritten as:

RuleFor(user => user.Surname).NotNull().WithMessage("Please ensure you have entered your {PropertyName}");

…and the value Surname will be inserted.

Placeholders in the FluentValidator

As shown in the example above, the message can contain placeholders for special values such as {PropertyName} – which will be replaced at runtime. Each built-in validator has its own list of placeholders.

The placeholders used in all validators are:

• {PropertyName} – Name of the property being validated

• {PropertyValue} – Value of the property being validated These include the predicate validator (Must validator), the email and the regex validators.

🔜 What’s the next thing you want us to cover in our SmaRF Knowledge Base?

Thank you for taking the time to read the article about FluentValidation. Our list of topics we wish to cover in the knowledge base is open to public, so feel free to reach out and suggest a topic. 

Or start a conversation with our team!

Leave a Reply

Your email address will not be published. Required fields are marked *