Dependency Injection with inheritance

866 views Asked by At

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

1

There are 1 answers

2
Cyril Durand On BEST ANSWER

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> to IValidator<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());