Validating Your ASP.NET APIs with FluentValidation and MediatR

Developing robust and scalable APIs is essential for modern web applications. One of the key aspects of creating a well-functioning API is validation, ensuring that data sent by clients conforms to the expected structure and format. In this blog post, we will explore how to effectively validate your ASP.NET APIs using FluentValidation and MediatR, two powerful libraries that enable clean and maintainable code.

The Promise of MediatR (and the Mediator Pattern and/or CQRS in General)

MediatR is a great library that allows you to make thin controllers — ASP.NET controllers with very little code, where the controllers merely offload the actual work to other classes. Well, what’s the point of that? It allows you to cleanly segregate your own application logic from framework-level concerns – authentication, authorization, routing, just to name a few. Doing so will make your code more unit-testable as you’re not having to provide mocks or shims for those framework-level pieces. If you’re unfamiliar with MediatR, please check out the introductory post that I did on the subject, a while back.

MediatR is based on an architectural pattern that came long before it called the Mediator pattern. In this principle, instead of having one entity take a direct dependency on another entity (tight-coupling), they instead deal with a third-party mediator that handles a given request and provides an appropriate response (loose-coupling). So for example, in an ASP.NET site, instead of a Controller (which is a framework level entity that has to deal with concerns such as routing and HTTP response codes and the like) having to also deal with your application-level logic, it simply offloads application work to MediatR that takes that application response, figures out the handler that can process it, issues and generates the correct response and hands it back to the requestor.

Command Query Responsibility Segregation (CQRS) is a somewhat related architectural pattern wherein requests are segregated into commands (a write-action — can be an INSERT, or a DELETE or an UPDATE) and queries (a read-action). The reasoning behind this approach is that you can then optimize for reads independent to other separate optimizations that you do for writes, without impacting each other. MediatR also loosely models this pattern with its IRequest interface. However, it does not make any distinction between commands and queries, just that you can implement both using this one interface.

Let’s look at a basic MediatR implementation. Let’s start with a simple request class for adding a new user into a system:

public class CreateUserRequest : IRequest<User>
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
}

Here, a consumer can pass a user’s information via this request class and expect a new User object in return. Now let’s look at a MediatR handler class that will process this request.

public class CreateUserHandler : IRequestHandler<CreateUserRequest, User>
{
    // Inject any required dependencies
    public CreateUserHandler(/* dependencies */)
    {
        // ...
    }

    public async Task<User> Handle(CreateUserRequest request, CancellationToken cancellationToken)
    {
        // Process the request and return the result
    }
}

Finally, let’s utilize this request and handler from an ASP.NET controller.

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IMediator _mediator;

    public UsersController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<ActionResult<User>> CreateUser([FromBody] CreateUserRequest request)
    {
        var result = await _mediator.Send(request);
        return CreatedAtAction(nameof(GetUser), new { id = result.Id }, result);
    }
}

Note the indirection and loose-coupling here. We’re not explicitly calling a handler but simply sending a request off to MediatR. It is the library’s responsibility to find the appropriate handler that can fulfill this request. That magic happens when you bootstrap MediatR in your application’s ASP.NET pipeline, usually in your Program.cs or Startup.cs.

builder.Services.AddMediatR(Assembly.GetExecutingAssembly());

MediatR will scan your assembly, produce a catalog of handlers and during runtime triage and offload requests to the correct handlers, as necessary.

Cross-Cutting Concerns in Your Application

When building web applications, you often must deal with some cross-cutting concerns – matters that affect the whole of the application.

Take for instance the matter of error-handling. You’ll probably want to emit errors in a consistent manner throughout your application. While you can meticulously catch and write handling logic in each handler contained in your application, that approach does not scale very well. You’ll soon end up with a lot of repetitive code or error-handling artifacts peppered all throughout your codebase. What if you can bubble up all errors to a global handler that can then provide a uniform response back to the clients?

Validation is another such cross-cutting concern. You’ll want to provide error messages back to your client in a consistent manner.

Enter IPipelineBehavior

Well, MediatR provides an elegant way of handling these cross-cutting concerns in the way of IPipelineBehavior. It behaves similarly to how middleware works in ASP.NET. It allows you to intercept an incoming request, execute some custom logic and pass the request off to a handler or alter the control flow in some other manner. Let’s use this mechanism to implement a global validator class that can intercept any incoming request and validate it before handing the request off to a handler.

using FluentValidation;
using MediatR;

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
     where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> validators;

    public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
    {
        this.validators = validators;
    }

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        if (validators.Any())
        {
            var context = new ValidationContext<TRequest>(request);

            var validationResults = await Task.WhenAll(
                validators.Select(v =>
                    v.ValidateAsync(context, cancellationToken)));

            var failures = validationResults
                .Where(r => r.Errors.Any())
                .SelectMany(r => r.Errors)
                .ToList();

            if (failures.Any())
                throw new Exceptions.ValidationException(failures);
        }
        return await next();
    }
}

We can wire up our validation behavior within our application during startup, like so:

builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

With this in place, our validation behavior will kick in with every incoming request. If MediatR sees that we have defined a Validator class for the given type of request, it will run those validations and if those validations result in one or more errors, it will not bother sending that request any further to the handler but instead, throw an exception (that can then be handled by a global error handler).

Now for the actual validation, I like to rely on another great open-source library called FluentValidation.

FluentValidation

FluentValidation is a popular .NET library for building strongly-typed validation rules. It allows developers to express validation logic using a fluent interface, leading to more readable and maintainable code. The syntax allows for very readable validations that read like plain English. Let’s build a basic validator class for our CreateUserRequest object.

public class CreateUserRequestValidator : AbstractValidator<CreateUserRequest>
{
    public CreateUserRequestValidator()
    {
        RuleFor(x => x.FirstName).NotEmpty();
        RuleFor(x => x.LastName).NotEmpty();
        RuleFor(x => x.Email).EmailAddress();
        RuleFor(x => x.Password).MinimumLength(6);
    }
}

And wire it up during our application startup, like so:

builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());

Putting it All Together

Note that we didn’t have to change our request object or handler or response or even the controller when we wired up validation. Everything is very loosely coupled (which is a great thing). It’s great that we can create such validators and the request objects or the handlers have no knowledge of them. MediatR will do the orchestration for us and execute the necessary validations for us before the request ever makes it to our handlers.

Closing Remarks

Wiring up validation like this allows us to add such functionality without cluttering up our business logic code with such concerns. There is a clear separation of layers. Such architecture will allow us to write clean code. It will allow us to contend with each of those layers separately while providing clarity within each.

Leave a Comment

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