Search code examples
c#unity-containerfluentvalidationmediatr

Validation in a Mediatr behaviour pipeline


I am using the Mediatr 4 with my web api 2 project. Together with FluentValidation and Unity I have been adding a pipeline behaviour to validate my requests.

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    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)
    {
        var context = new ValidationContext(request);
        var failures = _validators
            .Select(v => v.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

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

This all works fine but I would really like to be able to return the validation in a wrapped up response. I am struggling to make a change like this and either get it to compile or no have Unity throw runtime resolve issues.

I was thinking of having something like:

public class CommandResult : IResponseBase
{
    private List<ValidationFailure> _validationFailures = new List<ValidationFailure>();
    private readonly string _correlationid;

    public CommandResult(string correlationid)
    {
        _correlationid = correlationid;
    }
    public bool IsSuccess => _validationFailures.Count == 0;

    public static implicit operator bool(CommandResult result)
    {
        return result.IsSuccess;
    }

    public void AddFailures(List<ValidationFailure> results)
    {
        _validationFailures = results;
    }

    public List<ValidationFailure> Failures => _validationFailures;

    public string CorrelationId => _correlationid;
}

On this basis I add a constraint into the behaviour along the lines of:

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
    where TResponse : IResponseBase, new()

but trying to return a CommandResult rather than throwing an exception is giving me type conversion issues, and it feels like I am making it far too complicated and I am missing something quite basic.


Solution

  • Let me suggest another approach for your problem. Instead of a pipeline use custom ActionFilterAttribute to perform validation before your request hit controller and have to be routed by mediatr. The following example using Autofac as a container, but i hope you will get the idea and be able to modify the code appropriately. As a bonus - no changes are needed in your Mediatr requests or handlers. Validation will be performed before controller action called and execution won't go further until you have valid request.

    public class ValidateModelStateFilter : ActionFilterAttribute, IAutofacActionFilter
    {
        private readonly IValidatorFactory _factory;
    
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="factory"></param>
        public ValidateModelStateFilter(IValidatorFactory factory)
        {
            _factory = factory;
        }
    
        /// <summary>
        /// 
        /// </summary>
        /// <param name="actionContext"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            base.OnActionExecutingAsync(actionContext, cancellationToken);
    
            IEnumerable<object> parameters = actionContext.ActionArguments.Select(x => x.Value).Where(x => x != null);
            foreach (var parameter in parameters)
            {
                Type argumentType = parameter.GetType();
                if (argumentType == typeof(int) || argumentType == typeof(string))
                {
                    continue;
                }
                IValidator validator = _factory.GetValidator(argumentType);
                if (validator != null)
                {
                    ValidationResult validationResult = validator.Validate(parameter);
                    if (!validationResult.IsValid)
                    {
    
                       // place your formatting logic here. 
                        actionContext.Response = <your formatted response>;
                    }
                }
            }
    
            return Task.FromResult(0);
        }
    
    }
    

    }