FluentValidation inside a MediatR pipeline and Razor Pages

Viewed 606

I'm working on a project where everything is done through a mediatR pipeline.

To make things easy the pipeline is this : -> Validation -> Handler

The Validation layer is using FluentValidation and throws a ValidationException if something is wrong.

public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators)
    {
        _validators = validators;
    }

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

            var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
            var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();

            if (failures.Count != 0)
                throw new ValidationException(failures);
        }

        return await next();
    }
}

Everything is working fine up to now.

Now, I have my page handler like this :

[BindProperty]
public LoginCommand Data { get; set; }

public async Task<IActionResult> OnPostAsync(CancellationToken cancellationToken)
{
    try
    {
        await _mediator.Send(Data, cancellationToken);
        return LocalRedirect("/Index");
    }
    catch (ValidationException ex)
    {
        var res = new ValidationResult(ex.Errors);
        res.AddToModelState(ModelState, nameof(Data));
        return Page();
    }
}

Doing things like this works fine, the drawback is that it makes a lot of code for every page handler.

Is there a way to simply this so the handler would look like this :

[BindProperty]
public LoginCommand Data { get; set; }

public async Task<IActionResult> OnPostAsync(CancellationToken cancellationToken)
{
    await _mediator.Send(Data, cancellationToken);
    return LocalRedirect("/Index");
}

and have this part of the code everywhere like a filter / middleware

    try
    {
        // HANDLER CODE
    }
    catch (ValidationException ex)
    {
        var res = new ValidationResult(ex.Errors);
        res.AddToModelState(ModelState, nameof(VIEWMODEL_NAME));
        return Page();
    }
1 Answers

You could add a ValidationExceptionPipelineBehavior before the ValidationPipelineBehavior in the MediatR pipeline.

That would ensure all ValidationExceptions are caught.

Having an ExeptionPipelineBehavior is fairly common.

Related