DEV Community

Cover image for Fluent Validation in .NET Core - Advantages and Disadvantages
ByteHide
ByteHide

Posted on • Originally published at bytehide.com

Fluent Validation in .NET Core - Advantages and Disadvantages

In the thrilling world of hands-on coding with .NET Core, we come across various tools and techniques that make our lives as developers way easier. Always remember that every superhero has a toolkit, and for us, this includes the wonderful Fluent Validation.

However, every superhero must be aware of the strengths and weaknesses of their tools too.

Got the popcorn ready?🍿

We’re just getting started – let’s dive deep into the heart of Fluent Validation!

Leading Edge of Fluent Validation in .NET Core

Do you hear that? That’s the sound of your efficiency going through the roof!

Leveraging Fluent Validation for Greater Efficiency

The magic begins when you introduce Fluent Validation into your assembly. This puppy helps you to create more concise projects, removing the burden of databases and GUI, and lets you focus on the heart of the application – logic!

Let’s take a look at a quick example:

public class CustomerValidator: AbstractValidator<Customer> 
{
    public CustomerValidator() 
    {
        RuleFor(customer => customer.Name).NotEmpty()
                                          .WithMessage("Please enter a name");
    }
}
Enter fullscreen mode Exit fullscreen mode

From this example, you can see how Fluent Validation unleashes your ability to write validation rules directly in the business object’s code. Skip the details about the database or GUI, and focus only on the pure, essential logic.

Accelerated Development Cycle

Ain’t nobody got time for wasted time. With Fluent Validation, it’s like you’re adorning a cape and developing at light speed. Let’s talk about debugging, our old ‘frenemy’. How does Fluent Validation make debugging more like a friend and less like an enemy? You got it, by reducing time spent on resolving validation issues! More time for coffee, right?

Fluent Validation: A Deeper Dive into Flexibility and Extensibility

Let’s further unravel the dynamics of Fluent Validation to appreciate its exciting features – flexibility and extensibility.

Unleashing Flexibility in Validation Rules

In software development, as in life, flexibility can make or break our flow of work. It’s similar to driving a car – wouldn’t we prefer steering around obstacles rather than hitting them head-on? Fluent Validation allows us just that.

The flexibility that Fluent Validation offers in setting validation rules makes it a powerful tool for developers. Let’s revisit our User class example to capture this concept better with more validation rules demonstrating the high level of flexibility.

public class UserValidator: AbstractValidator<User> 
{
    public UserValidator() 
    {
        RuleFor(user => user.UserName).Matches(@"^[a-zA-Z][a-zA-Z0-9]*$")
                                      .WithMessage("Please specify a proper username.");

        RuleFor(user => user.Password).Matches(@"^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+=]).{8,}$")
                                       .WithMessage("Password must be complex, At least 8 characters long and include one number, one lowercase character, one uppercase character and one special character.");

        RuleFor(user => user.Email).EmailAddress()
                                   .WithMessage("Please specify a valid email address.");

        RuleFor(user => user.DateOfBirth).Must(BeAValidAge)
                                          .WithMessage("You must be at least 18 years old.");
    }

    private bool BeAValidAge(DateTime dateOfBirth)
    {
        int age = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth.Date > DateTime.Today.AddYears(-age)) age--;
        return age >= 18;
    }
}
Enter fullscreen mode Exit fullscreen mode

This extended example validates multiple properties of a user: the username, password, email, and age. The validation rules are highly flexible, allowing for different formats and criteria per property, such as a regular expression for the username or a custom function for age validation.

The beauty of this is how easily developers can adapt the rules to their requirements. With Fluent Validation, flexibility is always at our fingertips. It’s like turning into a validation-shapeshifter. Cool, eh?

Expanding Capabilities Through Extensibility

Imagine being in a restaurant where you can only choose from the menu but can’t request or suggest any custom dishes. Kind of restricting, huh? With Fluent Validation’s extensibility feature, this is not the case.

Apart from the built-in validators, Fluent Validation also allows the creation of custom validators to extend its capabilities, adding to the diversity of what developers can achieve with it.

Here’s a small glimpse of what we can do:

public class CustomValidator : AbstractValidator<User>
{
    public CustomValidator()
    {
        RuleFor(user => user).Custom((user, context) =>
        {
            if (user.Password.StartsWith(user.FirstName))
            {
                context.AddFailure("Password", "Password should not start with your first name.");
            }

            if (user.Password.StartsWith(user.LastName))
            {
                context.AddFailure("Password", "Password should not start with your last name.");
            }
        });
    }
} 
Enter fullscreen mode Exit fullscreen mode

In this example, we’re using the ‘Custom’ function to set up validation rules. It checks if the password starts with either the first or the last name of the user. The ‘context.AddFailure’ function helps in adding validation failures to the context which can be used later for error reporting.

Enhancing Code Readability With Fluent Validation

When it comes to software development, maintaining clean and readable code is synonymous to maintaining a tidy room; it’s vital for productivity and prevents issues down the line.

Fluent Validation emerges as a handy tool to help you keep your code neat, making reading and understanding your code by others, or “past you”, as breezy as a walk in the park!

Buckle up, as we delve into code readability and easy maintenance made possible by Fluent Validation.

Code Neatness and Improved Readability

One of the philosophies behind Fluent Validation is that validation rules are a property of the business domain, not the user interface or any other layer.

As such, with Fluent Validation we aim to streamline our code into clean, readable, and maintainable bits that adhere strictly to the Single Responsibility Principle. This means every validation class has one job – to validate!

Let’s look at a registration form for instance. There are fields the user needs to fill in such as account name, password, and email. Fluent Validation lets you encapsulate these validation rules in a tidy, single unit keeping your code neat and readable.

Here’s a simple example of how clean our code gets:

public class RegistrationValidator: AbstractValidator<Registration> 
{
    public RegistrationValidator() 
    {
        RuleFor(reg => reg.AccountName).NotEmpty().Length(5, 14)
                                       .WithMessage("Account Name should have 5-14 characters");
        RuleFor(reg => reg.Password).NotEmpty().Length(8,15)
                                    .WithMessage("Password should have 8-15 characters");
        RuleFor(reg => reg.Email).EmailAddress()
                                 .WithMessage("Please enter a valid email address");
    }
}
Enter fullscreen mode Exit fullscreen mode

As seen in the code snippet above, Fluent Validation keeps our code clean and neat, showcasing only the rules relevant to the registration form. Validation rules are intuitive and the organized structure ensures that your code is much more comprehensible to developers at any point in time.

Ease of Updation and Maintenance

Next up, we have another superhero quality of Fluent Validation – it makes the task of updating and maintaining code a less Herculean effort!

Let’s revisit our previous registration form scenario. Suppose we’re given a new requirement by our project manager to include validating that the password contains at least one letter, one number, and one symbol. Now, since we’re already using Fluent Validation, we just add this rule to the RegistrationValidator class.

public class RegistrationValidator: AbstractValidator<Registration> 
{
    public RegistrationValidator() 
    {
        RuleFor(reg => reg.AccountName).NotEmpty().Length(5, 14)
                                       .WithMessage("Account Name should have 5-14 characters");
        RuleFor(reg => reg.Password).NotEmpty().Length(8,15).Matches(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-z\d\s:]).*")
                                    .WithMessage("Password should have 8-15 characters, at least one letter, one number, and one symbol");
        RuleFor(reg => reg.Email).EmailAddress()
                                 .WithMessage("Please enter a valid email address");
    }
}
Enter fullscreen mode Exit fullscreen mode

Now wasn’t that smooth as silk? All we added was a single line of code, and our application is up-to-date with the new requirement. It’s easier to maintain or update your codebase, as the code responsible for validation is all in one place and not scattered around.

Simplifying Testing Efforts With Fluent Validation

If you’ve been around the coding block a couple of times, you would agree that testing, especially validation testing, is a vital aspect of application development.

While the concept of testing often sends some developers running for the hills, Fluent Validation in .NET has made the process a far less daunting task, almost like a soothing calm before the coding storm.

Let’s look at how its uses in unit testing can be as smooth and sweet as a chocolate-filled candy.

Simplification of Unit Testing

Unit testing involves testing various pieces of your application in isolation, like pieces of a puzzle. This task can feel like a Herculean challenge when rules and validation logic are scattered like small puzzle pieces all over the codebase. Fluent validation turns this often confusing and complex task into a simplified, streamlined process.

Have you ever struggled to set up your unit tests because of intricate relationships between validations or have found yourself juggling between different components of an application to figure out what goes where? Let’s see how Fluent Validation helps to curb these issues.

Imagine we have a long, complex customer registration form. Using Fluent Validation in .NET, you can consolidate all validation rules into a clear, neat validator class, like this:

public class CustomerValidator: AbstractValidator<Customer> 
{
    public CustomerValidator() 
    {
        RuleFor(customer => customer.Name).NotEmpty()
                                          .Length(1, 100);
        RuleFor(customer => customer.Email).EmailAddress();
        RuleFor(customer => customer.Password).NotEmpty()
                                              .Length(6, 20);
        RuleFor(customer => customer.Address).Length(0, 500);
    }
}
Enter fullscreen mode Exit fullscreen mode

This makes it simple to verify compliance with each rule, and you can then perform unit testing focusing on individual rules without having all the pieces in play.

Let’s consider a case where we need to test if customer registration fails when the Name field is empty. Using Fluent Validation and Xunit, you can easily set up and perform this test:

public class CustomerValidatorTests 
{
    private readonly CustomerValidator _validator = new CustomerValidator();

    [Fact]
    public void Should_Fail_When_Name_Is_Empty() 
    {
        var customer = new Customer {
            Name = string.Empty,
            Email = "email@example.com",
            Password = "password",
            Address = "100 Main st."
        };

        var result = _validator.Validate(customer);

        Assert.False(result.IsValid);
    }
}
Enter fullscreen mode Exit fullscreen mode

This specific test method checks that the Fluent Validation rules correctly flag an invalid customer registration attempt where the Name is empty. If the test case passes, you can rest easy knowing your validation rule is doing exactly what you expect.

Next!

Predictable Validation Outcomes

One of the nightmares of any developer is unpredictability in the codebase. Well, who really likes a wild card when coding? Fluent Validation takes this stress away, making validation outcomes more predictable.

Let’s take the above CustomerValidator with a set of rules. The validator would only pass the validation when none of the rules is violated. This makes it predictable as the rules are clear and the validator does not throw any unexpected curveballs.

public class CustomerValidator: AbstractValidator<Customer> 
{
    public CustomerValidator() 
    {
        RuleFor(customer => customer.Name).NotEmpty()
                                          .Length(1, 100);
        RuleFor(customer => customer.Email).EmailAddress();
        RuleFor(customer => customer.Password).NotEmpty()
                                              .Length(6, 20);
        RuleFor(customer => customer.Address).Length(0, 500);
    }
}
Enter fullscreen mode Exit fullscreen mode

With the above validator, you can anticipate the outcome of validation based on what data is provided. For instance, a customer instance with a Name field shorter than 1 character or longer than 100 characters will fail validations.

Similarly, a customer instance with an Email field that doesn’t match the standard email address format will also fail validations.

Potential Drawbacks of Fluent Validation in .NET Core

Fluent Validation in .NET core, like any other tool, isn’t devoid of cons. While its benefits are tantalizing, it’s crucial to address some of its potential drawbacks, specifically the steep learning curve and potential performance impacts. Understanding these pitfalls can help avoid nasty surprises during application development and deployment.

Learning Curve and Initial Complexity

Let’s start with the learning curve. Sure, Fluent Validation could look intimidating at first glance. The fluent syntax might come off as strange, especially if you’re new to .NET Core and Fluent Validation. Peep this sample code:

public class CustomerValidator: AbstractValidator<Customer> 
{
    public CustomerValidator() 
    {
        RuleFor(customer => customer.Name).NotEmpty()
                                          .Length(1, 100)
                                          .WithMessage("Name must be between 1 to 100 characters");
    }
}
Enter fullscreen mode Exit fullscreen mode

In the code snippet above, we have a RuleFor method with specific conditions for the customer.Name property. While veteran developers might understand this at first glance, beginners could find this syntax relatively complex.

Interestingly, this complexity isn’t as bad as it looks. Sure, you might spend extra time with the documentation at the start. But once you start piecing together the way Fluent Validation works, you might find that the benefits truly outweigh the initial complexity.

Hey, eventually, even time-consuming tasks like complex form validations turn out to be a breeze!

Possible Performance Impact

Shifting gears, let’s speak about the potential performance impact. Now, Fluent Validation operates on rule chains, and each chain corresponds to a property of the model being validated. Extensive chains with overly complex rules could, in theory, slow down your application. For instance, combining multiple validators, like so:

public class InviteValidator : AbstractValidator<Invite>
{
    public InviteValidator()
    {
        RuleFor(invite => invite.Email).NotEmpty()
                                       .EmailAddress()
                                       .WithMessage("A valid email is required");
        RuleFor(invite => invite.Code).NotEmpty()
                                      .Length(10)
                                      .WithMessage("Invitation code must contain exactly 10 characters!");
    }
}
Enter fullscreen mode Exit fullscreen mode

The code snippet above checks both the email and code fields. If every field in your model has such multiple complex validations, it could slow things down a bit over time, especially for large models validating numerous records.

It doesn’t mean you should be scared away from Fluent Validation, though. With mindful writing and efficient use of rules, even this issue can be mitigated. As a developer, it’s about knowing how and when to use things. Pace yourself, use only what you need, and ensure performance stays at its optimal level.

In conclusion, Fluent Validation is a powerful tool in your .NET Core toolbox, but remember that every tool has its strengths and weaknesses. Balancing the pros and cons will help you make an effective decision tailored to your specific programming needs. The power to choose wisely lies in your capable hands.

Top comments (1)

Collapse
 
martinhc profile image
Martin Humlund Clausen

Awesome article Thank you for sharing! 🦄

I find it interesting that you mention performance. More likely if not, I would always optimise for readability and maintainability, since no-one wants to deal with highly optimised code for marginal gains. Of course there is always cases where we need optimize, however 99.5% of application would benefit more from readability, than squeezing the last bit of performance out of the CPU 🤓

Fluent Validation is just one of those things, that makes everybody's life easier 🚀