Using Ardalis.Result in a MediatR pipeline
I've successfully implemented Ardalis.Result coupled with FluentValidation and MediatR in my project and now I wanted to take it one step further by using a PipelineBehavior to handle my validation, so I don't need to manually write the call to my validators in my command handlers anymore. However, I'm running into a bit of a problem with type conversions and the way access in the Ardalis.Result library is restricted, so I wanted to check if maybe I'm missing something.
My current setup, pre-behavior, is something like this:
public async Task<Result<MyResponse>> Handle(MyCommand request, CancellationToken cancellationToken)
{
var validationResult = await _validator.ValidateAsync(request, cancellationToken);
if (!validationResult.IsValid)
{
return Result.Invalid(validationResult.AsErrors());
}
// execute business logic and return the result of type MyResponse
}
This works great because of the implicit operators that are available in the library; I can simply write the same code in all my commandhandlers, and the operators will handle the correct typing. In this case, I will have a Result<MyResponse> with an empty value and Status Invalid, which I can then map as a 400 http response.
Then I thought that this would be perfect for a behavior because it's always the same code anyway and Ardalis will handle the type conversions, but due the constraints on how the Behaviors work and the way generics are implemented in Ardalis (Result is a Result<T> but not the other way around) I couldn't quite get it to work the way I wanted to.
Eventually, I came to this (working) solution:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : class, IResult // can't use Result here, because Result<T> doesn't inherit from it; can't use Result<T> because we don't know what T is
{
private readonly IEnumerable<IValidator<TRequest>>? _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
if (_validators == null || !_validators.Any())
{
// if there are no validators, just continue to the next behavior/handler in the pipeline
return await next(cancellationToken);
}
var context = new ValidationContext<TRequest>(request);
// execute all known validators and collect the results
var validationResults = await Task.WhenAll(
_validators.Select(validator => validator.ValidateAsync(context)));
// we're only interested in the results that are not valid
var validationFailureResults = validationResults
.Where(validationResult => !validationResult.IsValid)
.ToList();
if (validationFailureResults.Any())
{
var validationErrors = validationFailureResults.SelectMany(validationResult => validationResult.AsErrors());
var response =
// use reflection to get the properly typed method for Result.Invalid; otherwise the casting doesn't work
typeof(TResponse)
.GetMethod(nameof(Result.Invalid), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public, [typeof(IEnumerable<ValidationError>)])?
.Invoke(null, [validationErrors]) as TResponse
// fallback to the generic Result.Invalid if the specific method is not found, but this is unlikely to work, because the types won't match
?? Result.Invalid(validationErrors) as TResponse;
return response
// hail mary just so I wouldn't have to return null; shouldn't happen
?? throw new ValidationException(validationFailureResults.SelectMany(x => x.Errors));
}
// If validation passes, continue to the next behavior/handler in the pipeline
return await next(cancellationToken);
}
}
My problem is that I have to use reflection which seems like overkill here, but I can't simply return Result.Invalid, because it won't compile due to unmatching types. There's also no constraints that I can seem to add to make it work without explicit casting because my results are not actually of type Result, but of type Result<T>, but at this point I don't know what T is and .NET doesn't allow writing Result<> in a generic constraint.
I also can't make the Result object myself, because all but the default constructor are marked as protected as are the property setters.
In the end, the only way I could think of to call the Invalid method on the correct type was with reflection, but it doesn't feel like the best way to go about this.
If I'm going about this all wrong and there's a better way to do this, I'd happily accept your suggestions.
If I'm right, and there currently isn't a better way to do this, would it perhaps be an option to give consumers of your library more control about constructing the Results themselves, or perhaps making non-generic overloads that accept a type as a parameter?
I don't actually think the second one would help much, as I still wouldn't know what T is without reflection, but it still seems like it might be useful in other use cases.