0

I am playing around with a test project. I am trying to implement a CQS pattern and I am getting close to wrapping up the initial tests. I have run into an issue with trying to resolve my IQueryValidtor<> and IQueryHandler<,> classes. The method resolves them alright, but when i try to access the interface methods implemented in the concrete class, i get

The best overloaded method match for 'MyProjectsNamespace.GetSiteValidator.Validate(MyProjectsNamespace.Queries.GetSite)' has some invalid arguments.

I am basing my code on this answer I found. Everything appears to be lining up during design time, but run time is a different story.

I am including all of the interfaces, implementations, and unit tests that I am working with on this issue. The first unit test is actually working. It is the last two that are actually trying to use the resolved classes. I am using Autofac for my dependency injection.

public interface IQuery<TResult> {
}

public class GetSite : IQuery<Site> {
    public Guid Id { get; set; }
}

public interface IValidator<T> {
    Task<List<ValidationResult>> Validate(T command);
}

public class GetSiteValidator : IValidator<GetSite> {
    public async Task<List<ValidationResult>> Validate(GetSite command) {
        List<ValidationResult> results = new List<ValidationResult>();

        if(command == null) {
            throw new ArgumentNullException(nameof(command));
        }

        if(command.Id == Guid.Empty) {
            results.Add(new ValidationResult() { FieldName = "Id", Message = "Is empty" });
        }

        return results;
    }
}

public interface IQueryHandler<in TQuery, TResult> where TQuery : IQuery<TResult> {
    Task<TResult> Handle(TQuery query);
}

public class GetSiteHandler : IQueryHandler<GetSite, Site> {
    public Task<Site> Handle(GetSite query) {
        throw new NotImplementedException();
    }
}

The code above is all of the interfaces and concrete classes used in this problem. The code below is the dispatcher class where i am having the issue.

public class QueryDispatcher : IQueryDispatcher {
    private readonly IComponentContext _context;

    public QueryDispatcher(IComponentContext context) {
        _context = context;
    }

    public async Task<TResult> Dispatch<TResult>(IQuery<TResult> query) {
        if (query == null) {
            throw new ArgumentNullException(nameof(query), "Query cannot be null");
        }

        // use dynamic datatype because the tresult is not known at compile time
        var validatorType = typeof(IValidator<>).MakeGenericType(query.GetType());
        dynamic validator = _context.Resolve(validatorType);

        var handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
        dynamic handler = _context.Resolve(handlerType);

        List<ValidationResult> errors = await validator.Validate(query);

        if(errors.Count == 0) {
            return await handler.Handle(query);
        } else {
            // raise failed validation event
            throw new ApplicationException("Not implemented");
        }
    }
}
Community
  • 1
  • 1
fizch
  • 2,599
  • 3
  • 30
  • 45

1 Answers1

2

You are passing an IQuery<TResult> query object to the Validate(GetSite) method - the method requires a GetSite type.

Even though GetSite implements IQuery<TResult>, The compiler can't guarantee that IQuery<TResult> query is an object of type GetSite - it could also be some other type implementing IQuery<TResult> where a cast is not possible.

khargoosh
  • 1,450
  • 15
  • 40
  • but GetSite is an IQuery – fizch Apr 05 '17 at 23:47
  • 1
    `GetSite` implements `IQuery`, but there is no guarantee that all classes implementing `IQuery` are `GetSite` or can be cast. Your method requires a `GetSite` type, this is not the same thing. – khargoosh Apr 05 '17 at 23:49
  • @fizch is query an `IQuery`? Because then you're doing the equivalent of passing something of type `object` to a function that expects any other class type. – Millie Smith Apr 05 '17 at 23:50
  • 1
    Coveriance and contravariance in action. Well spotted, @khargoosh – trailmax Apr 05 '17 at 23:50
  • If there is a class `Apple : IFruit` and another class `Orange : IFruit`, a method `Eat(Apple)` cannot simply accept any type of `IFruit` because not all classes implementing `IFruit` can be guaranteed to be of type `Apple` (could be an `Orange` or a `Banana` - `Eat()` ing these fruits could be very different indeed! – khargoosh Apr 05 '17 at 23:53
  • Ok, so how do i fix it? I am trying to get the GetSiteValidator class to implement the IValidator for GetSite and as we know, GetSite is an IQuery. – fizch Apr 05 '17 at 23:56
  • You can try to cast IQuery to GetSite before passing it in to the method, or you can change the method signature to accept type IQuery and adjust the method accordingly. – khargoosh Apr 06 '17 at 00:01
  • If I change the Validate method to accept an IQuery, I can no longer create Validators for other query types (e.g. IQuery). Likewise, the dispatch method (the method with the errors) is meant to be generic enough to just take some type of query, load the necessary validator/handler and execute the known methods. I don't think that I can convert query to IQuery. Am I missing something? – fizch Apr 06 '17 at 00:13
  • @fizch Why can't you cast `query` to a `GetSite` type before passing it to the method? – khargoosh Apr 06 '17 at 00:20
  • Because in that method, i do not know that it is a GetSite. All I know is that it supposed to validate and IQuery. I'll add the code for that entire class again. – fizch Apr 06 '17 at 00:26
  • @fizch At the end of the day, you are attempting to call a method that requires a specific type because it uses a property of that type. Will all of your `IQuery` implementing classes have an `Id` property? If so, move the property to the interface, and change the signature to require the interface. – khargoosh Apr 06 '17 at 00:26
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/140020/discussion-between-fizch-and-khargoosh). – fizch Apr 06 '17 at 00:29