'Return response with errors instead of throwing exception in validation pipeline mediatr 3

I am currently working with Pipeline behavior in Mediatr 3 for request validation. All the examples that I came across were throwing ValidationException if any failures happening, instead of doing that I want to return the response with the error. Anyone has idea on how to do it?

Below is the code for the validation pipeline:

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

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

    public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
    {
        var failures = _validators
            .Select(v => v.Validate(request))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Any())
        {
            throw new ValidationException(failures);
        }

        return next();
    }
}

Note: I found this question Handling errors/exceptions in a mediator pipeline using CQRS? and I am interested in the 1st option on the answer, but no clear example on how to do that.

This is my response class:

public class ResponseBase : ValidationResult
{
    public ResponseBase() : base() { }

    public ResponseBase(IEnumerable<ValidationFailure> failures) : base(failures)  {
    }
}

and I added below signature in the validation pipeline class:

public class ValidationPipeline<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> 
where TResponse : ResponseBase

I did this then in the Handle method:

var response = new ResponseBase(failures);
return Task.FromResult<TResponse>(response);

But that gave me error 'cannot convert to TResponse'.



Solution 1:[1]

Simply don't call next if there's any failures:

public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
    var failures = _validators
        .Select(v => v.Validate(request))
        .SelectMany(result => result.Errors)
        .Where(f => f != null)
        .ToList();

    if (failures.Any())
    {
        var response = new Thing(); //obviously a type conforming to TResponse
        response.Failures = failures; //I'm making an assumption on the property name here.

        return Task.FromResult(response);
    }
    else
    {
        return next();
    }
}

Note:
Your class (Thing in my example) must be of type TResponse

Solution 2:[2]

Several years ago, I created general Result object, which I am constantly improving. It is quite simple, check https://github.com/martinbrabec/mbtools.

If you will be ok with the Result (or Result<>) being the return type every method in Application layer, then you can use the ValidationBehavior like this:

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
    where TResponse : Result, new()
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

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

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

            List<ValidationFailure> failures = _validators
                .Select(v => v.Validate(context))
                .SelectMany(result => result.Errors)
                .Where(f => f != null)
                .ToList();

            if (failures.Any())
            {
                TResponse response = new TResponse();

                response.Set(ErrorType.NotValid, failures.Select(s => s.ErrorMessage), null);

                return Task.FromResult<TResponse>(response);
            }
            else
            {
                return next();
            }
        }

        return next();
    }

}

Since all your handlers return Result (or Result<>, which is based upon Result), you will be able to handle all validation errors without any exception.

Solution 3:[3]

You can configure validation handling using package https://www.nuget.org/packages/MediatR.Extensions.FluentValidation.AspNetCore

Just insert in configuration section:

services.AddFluentValidation(new[] {typeof(GenerateInvoiceHandler).GetTypeInfo().Assembly});

GitHub

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Harold Trotter
Solution 3 GetoX