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.
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);
}
}
}