I'm using a pipeline build with MediatR. I'm adding a simple behavior aimed at validating queries and commands:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<Result>
where TResponse : Result
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators ?? Enumerable.Empty<IValidator<TRequest>>();
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
(...)
}
}
Currently most of my queries inherits from PaginatedQuery.
public abstract class PaginatedQuery : IQuery
{
public int Offset { get; set; } = 0;
public int Limit { get; set; } = 25;
}
Example:
public class GetCountriesQuery : PaginatedQuery
{
public GetCountriesQuery(PaginatedInput input)
{
Limit = input.Limit;
Offset = input.Offset;
}
}
To make sure users don't send invalid Offset/Limit, I have built the following validator:
public class PaginatedQueryValidator : AbstractValidator<PaginatedQuery>
{
public PaginatedQueryValidator()
{
RuleFor(p => p.Offset)
.GreaterThanOrEqualTo(0)
.WithMessage("Offset must be greater or equal to 0");
RuleFor(p => p.Limit)
.GreaterThan(0)
.WithMessage("Limit must be greater than 0");
}
}
Unfortunately this validator is NOT injected into my ValidationBehavior even though TRequest is inheriting from PaginatedQuery.
In the end, I would need to inject validators for the current TRequest AND also for all mother classes (ie: PaginatedQuery).
Is this something achievable?
Note: To inject validators, I scan for all types implementing IValidator<> (using Scrutor)
services.Scan(x =>
x.FromAssembliesOf(typeof(Startup))
.AddClasses(c =>
c.AssignableToAny(typeof(IValidator<>)))
.AsImplementedInterfaces()
);
Edit: I forgot to mention that I'm using AutoFac
When Autofac resolves a ValidationBehavior<GetCountriesQuery, GetCountriesResponse>
you expect it to resolve a IValidator<GetCountriesQuery>
whereas only a IValidator<PaginatedQuery>
is registered and IValidator<PaginatedQuery>
is not assignable to IValidator<GetCountriesQuery>
You can try using the following C# statement
IValidator<GetCountriesQuery> i = (IValidator<PaginatedQuery>)null;
The C# compiler will give you this error message
Error CS0266 : Cannot implicitly convert type
IValidator<PaginatedQuery>
toIValidator<GetCountriesQuery>
. An explicit conversion exists (are you missing a cast?)
In order to make it work, C#4 introduces covariance and contravariance for generic type parameter. It allows to cast IValidator<Base>
to IValidator<Derived>
or the opposite. See covariance and contravariance on MSDN for more information.
In your case you need contravariance which can be declared by using the in
keyword
public interface IValidator<in TRequest> { }
In order to make Autofac works with contravariant you have to register a the ContravariantRegistrationSource
registration source :
builder.RegisterSource(new ContravariantRegistrationSource());