Search code examples
c#.netdependency-injectioncastle-windsor

Resolving mixed open-closed generics with Castle Windsor


I'm trying to resolve a mixed open-closed generic type with constraints using Castle Windsor. This should be able to resolve any open generics if Foo implements IFoo<>:

container.Register(Component.For(typeof(IFoo<>).ImplementedBy(typeof(Foo<>)));

My situation is slightly more complex:

I have the following Handler class:

public abstract class CommandHandlerBase<TCommand, TResponse>
    : IRequestHandler<TCommand, TResponse>
    where TCommand : IRequest<TResponse>
{
    public abstract Task<TResponse> Handle(
        TCommand request, CancellationToken cancellationToken);
}

public class AddMasterDataEntityCommandHandler<TNewEntityData>
    : IRequestHandler<TNewEntityData, Response>
    where TNewEntityData : IRequest<Response>
{
    public Task<Response> Handle(
        TNewEntityData request, CancellationToken cancellationToken)
    {
       // ...
    }
}

The idea is that AddMasterDataEntityCommandHandler will be a generic command handler that can handle any type of contract of type TNewEntityData.

Since I'm using Mediatr, my contracts have to implement IRequest<,>, which they do. In this case, I'm enforcing that all the handlers should return a Response.

Example usage:

Response response = await mediator.Send(new AddMasterDataEntityCommand<NewPlace>());

I created a simple console app to isolate this behavior:

    public static void Main(string[] args)
    {
        var container = new WindsorContainer();

        container.Register(Types.FromThisAssembly()
                                .BasedOn(typeof(IRequestHandler<,>))
                                .Unless(t => t.IsAbstract || t.IsInterface)
                                .WithServices(typeof(IRequestHandler<,>))
                                .LifestyleTransient());

        var instance = container.Resolve(typeof(IRequestHandler<NewData, Response>));
    }

However, a test throws an exception indicating an error in my code:

Castle.MicroKernel.Handlers.GenericHandlerTypeMismatchException: 'Types ConsoleApp4.NewData, ConsoleApp4.Response don't satisfy generic constraints of implementation type ConsoleApp4.AddMasterDataEntityCommandHandler'1 of component 'ConsoleApp4.AddMasterDataEntityCommandHandler'1'. This is most likely a bug in your code.'

I don't see the issue here, CW should be able to resolve open/closed generics, right? Furthermore, the problem seems to be related to the additional Response parameter as a TResponse type. Did I register the component the wrong way? I'm confident I didn't mess up the generic constraints...

Thanks in advance to anyone who can take a look.


Solution

  • Krzysztof Kozmic led me into the right direction:

    I initially tried an implementation with IGenericImplementationMatchingStrategy but couldn't get this working to it only being able to handle a type with one generic. I ended up registering like this with a bit of reflection magic:

    private void RegisterGenericMasterDataCommandHandlers(IWindsorContainer container) {
        foreach (Type contractType in contractTypes) {
            Type requestedType = typeof(IRequestHandler<,>).MakeGenericType(typeof(AddMasterDataEntityCommand<>).MakeGenericType(contractType), typeof(Response));
            Type implementedType = typeof(AddMasterDataEntityCommandHandler<>).MakeGenericType(contractType);
    
            container.Register(Component.For(requestedType, implementedType));
        }
    }