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 aIValidator<GetCountriesQuery>
whereas only aIValidator<PaginatedQuery>
is registered andIValidator<PaginatedQuery>
is not assignable toIValidator<GetCountriesQuery>
You can try using the following C# statement
The C# compiler will give you this error message
In order to make it work, C#4 introduces covariance and contravariance for generic type parameter. It allows to cast
IValidator<Base>
toIValidator<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
keywordIn order to make Autofac works with contravariant you have to register a the
ContravariantRegistrationSource
registration source :