Ninject just won't register MediatR.IRequestHandler<,> using convention-based binding?

567 Views Asked by At

As per following example:

MediatR.Examples.Ninject

I've a MediatorModule class as follows:

    public class MediatorModule : NinjectModule {
        public class ContravariantBindingResolver : NinjectComponent, IBindingResolver {
            public IEnumerable<IBinding> Resolve(Multimap<Type, IBinding> bindings, Type service) {
                if (service.IsGenericType) {
                    var genericType = service.GetGenericTypeDefinition();
                    var genericArguments = genericType.GetGenericArguments();

                    if (1 == genericArguments.Count() && genericArguments.Single().GenericParameterAttributes.HasFlag(GenericParameterAttributes.Contravariant)) {
                        var argument = service.GetGenericArguments().Single();
                        var matches = bindings.Where(kvp => kvp.Key.IsGenericType
                                                         && kvp.Key.GetGenericTypeDefinition().Equals(genericType)
                                                         && kvp.Key.GetGenericArguments().Single() != argument
                                                         && kvp.Key.GetGenericArguments().Single().IsAssignableFrom(argument))
                                              .SelectMany(kvp => kvp.Value);
                        return matches;
                    }
                }

                return Enumerable.Empty<IBinding>();
            }
        }
        public override void Load() {
            Kernel.Components.Add<IBindingResolver, ContravariantBindingResolver>();
            Kernel.Bind(services => services.FromAssemblyContaining<IMediator>().SelectAllClasses().BindDefaultInterface());
            Kernel.Bind(services => services.FromThisAssembly().SelectAllClasses().InheritedFrom(typeof(IRequestHandler<,>)).BindAllInterfaces());
            Kernel.Bind(services => services.FromThisAssembly().SelectAllClasses().InheritedFrom(typeof(INotificationHandler<>)).BindAllInterfaces());
            Kernel.Bind(typeof(IPipelineBehavior<,>)).To(typeof(RequestPreProcessorBehavior<,>));
            Kernel.Bind(typeof(IPipelineBehavior<,>)).To(typeof(RequestPostProcessorBehavior<,>));
            Kernel.Bind(typeof(IPipelineBehavior<,>)).To(typeof(RequestExceptionActionProcessorBehavior<,>));
            Kernel.Bind(typeof(IPipelineBehavior<,>)).To(typeof(RequestExceptionProcessorBehavior<,>));
            Kernel.Bind<ServiceFactory>().ToMethod(ctx => t => ctx.Kernel.TryGet(t));
        }
    }

On a Quick Watch over services.FromThisAssembly().SelectAllClasses().InheritedFrom(typeof(IRequestHandler<,>)), I can see that the classes are correctly found.

Quick Watch

And here is an example of my command and handler.

public class SupplierInvoice {
    public class ProcessCommand : IRequest<ProcessedTransaction> {
        public ProcessCommand(XmlDocument supplierInvoiceDocument)
            => SupplierInvoiceDocument = supplierInvoiceDocument;

        public XmlDocument SupplierInvoiceDocument { get; }
    }

    public class ProcessCommandHandler : IRequestHandler<ProcessCommand, ProcessedTransaction> {
        private readonly IXmlSerializer<SupplierInvoice> _serializer;
        private readonly IValidator<SupplierInvoice> _validator;
        private readonly IMediator _mediator;
        public ProcessCommandHandler(IXmlSerializer<SupplierInvoice> serializer, IValidator<SupplierInvoice> validator, IMediator mediator) {
            _serializer=serializer;
            _validator=validator;
            _mediator=mediator;
        }

        public async Task<ProcessedTransaction> Handle(ProcessCommand request, CancellationToken cancellationToken) {
            if (request == null) return ProcessedTransaction.Succeeded;

            var model = _serializer.Deserialize(request.SupplierInvoiceDocument.OuterXml);
            var vr = _validator.Validate(model);
            if (!vr.IsValid) // throwing exception with validation messages.

            return await _mediator.Send(new CreateCommand(model));
        }
    }
}

So I wonder how can the handlers be not registered using the BindAllInterfaces method?

Even using plain old binding syntax, the request handlers just won't get registered.

Kernel.Bind<IRequestHandler<SupplierInvoice.ProcessCommand, ProcessedTransaction>>().To<SupplierInvoice.ProcessCommandHandler>();

What am I missing?

1

There are 1 best solutions below

3
On BEST ANSWER

As per linked Ninject example: MediatR.Examples.Ninject, I guess I found an error in the ContravariantBindingResolver.cs class.

The line that I guess is at fault is when getting the generic arguments for the second time.

var genericType = service.GetGenericTypeDefinition();
var genericArguments = genericType.GetGenericArguments();
if (genericArguments.Count() == 1
   && genericArguments.Single().GenericParameterAttributes.HasFlag(GenericParameterAttributes.Contravariant))
{
    var argument = service.GetGenericArguments().Single();
    var matches = bindings.Where(kvp => kvp.Key.IsGenericType
                                   && kvp.Key.GetGenericTypeDefinition().Equals(genericType)
                                   && kvp.Key.GetGenericArguments().Single() != argument
                                   && kvp.Key.GetGenericArguments().Single().IsAssignableFrom(argument))
                        .SelectMany(kvp => kvp.Value);
   return matches;
}

Notice that var genericArguments = genericType.GetGenericArguments() uses the generic type definition returned by the service.GetGenericTypeDefinition() method call. But because in the sample code provided the second call to GetGenericArguments is made from the service instance, the correct generic parameter doesn't seem to be returned adequately. Hence I'd recommend the use of the already declared variable which contains the generic arguments from which the argument variable's value can be obtained.

To sum it up, for me what made the resolver actually resolve the correct handler every time is that I changed this line:

var argument = service.GetGenericArguments().Single()

to

var argument = genericArguments.Single()

given it is contravariant, so only available argument since the check is already made for the length of the returned arguments array.

Changing this for me made the difference between the unregistered handler exception to a working code able to resolve proper handlers.