Today I would like to show you a really cool way to validate MediatR commands and queries using FluentValidation.

The OK way

Fine. Each of us validates commands and queries in some way. After all, they are the input. The vast majority of the validation code I’ve seen so far has been integrated with command and query handlers.

Here is an example handler code, which validates a command input.

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand>
{
    public async Task<Unit> Handle(
        CreateUserCommand request,
        CancellationToken cancellationToken)
    {
        // Some command validation logic e.g.
        if (string.IsNullOrEmpty(request.Username))
        {
            throw new ArgumentException($"Username cannot be empty");
        }

        // User creation logic
    }
}

There is nothing bad in that, but… What if we separate validation and command logic? Wouldn’t it be more maintainable?

Let’s separate it!

When I write about separation, I don’t mean extracting validation logic into a private method. It wouldn’t be a bit elegant. I’d like to extract all validation logic out of the handler. To achieve this, I am going to use the FluentValidation library.

Let’s implement our CreateUserCommand validator.

public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand> {
  public CustomerValidator() {
    RuleFor(x => x.Username)
        .NotEmpty()
        .WithMessage("Username cannot be empty");
    RuleFor(x => x.Email)
        .NotEmpty()
        .WithMessage("Email address cannot be empty");
    RuleFor(x => x.Email)
        .EmailAddress()
        .WithMessage("Please specify a valid email address");
    // ...
  }
}

Of course, we have to register all our validators in the DI container. We can do it manually, but I recommend doing it using extensions from the extra NuGet package. Alternatively you can do it using Scrutor.

Then we have to modify our CreateUserCommandHandler to use our brand new CreateUserCommandValidator:

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand>
{
    private readonly IValidator<CreateUserCommand> validator;

    public CreateUserCommandHandler(
        IValidator<CreateUserCommand> validator) // AbstractValidator implements IValidator interface
    {
        _validator = validator;
    }

    public async Task<Unit> Handle(
        CreateUserCommand request,
        CancellationToken cancellationToken)
    {
        // Our whole validation code becomes:
        validator.ValidateAndThrow(request);

        // User creation logic
    }
}

Nice, but can we do it better? Well, of course we can.

Let’s call our validator implicitly

The last improvement will be to completely remove the explicit validation from the handler. Now it should look like this:

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand>
{
    public async Task<Unit> Handle(CreateUserCommand request, CancellationToken cancellationToken)
    {
        // User creation logic
    }
}

Now I have to tell you something important about MediatR. It allows you to implement something like middlewares. It’s called pipeline behavior.

Behaviors allow you to build your own pipeline that will be executed before the handler. Let’s implement our ValidationBehavior.

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly IValidator<TRequest> _validator;

    public ValidationBehavior(IValidator<TRequest> validator)
    {
        _validator = validator;
    }

    public Task<TResponse> Handle(
        TRequest request,
        CancellationToken cancellationToken,
        RequestHandlerDelegate<TResponse> next)
    {
        _validator.ValidateAndThrow(request); // Check out the other methods for more advanced handling of validation errors 
        return next();
    }
}

Finally, register it in a DI container. That’s it! Now, every time you run a command or query, it will be validated by the appropriate validator.

Nothing prevents you from implementing more validators for one command/query, but it will require modification of our LoggingBehavior. I’ll leave it for you as homework.

Summary

I hope you learned something interesting today. What do you think of this kind of separation? I think this makes the code easier to maintain.

I also decided to write a small demo that you can find in this GitHub repository. Enjoy!