Thursday, July 7, 2016

Cannot call action method on controller because the action method is a generic method

I have been living my life writing web apps with MVC5 until I ran into this weird problem. It happened because I put a generic method in my controller (I had a base controller, but that's by the way). I implemented some functionalities using Command Pattern and thought I could retrieve my command handler within a controller using the simple call:

var handler = CommandHandler<TCommand>();
handler.Handle(command);

Well, the error above happened and I ended up with a solution where I now call

var processor = MyIoCContainer.GetInstance<ICommandProcessor>();
processor.Process(command);

It's an extra level of indirection, but sire, it worked perfectly! Here is how I arrived at that:

Suppose, following the Command Pattern, you have this handler interface:

public interface ICommandHandler<TCommand> where TCommand : ICommand //ICommand is a marker interface
{
    void Handle(TCommand command);
}

You define the ICommandProcessor interface:

public interface ICommandProcessor
{
    void Process(ICommand command);
}

with a corresponding implementation:

public sealed class CommandProcessor : ICommandProcessor
{
    private IServiceProvider _serviceProvider;
    public CommandProcessor(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void Process(ICommand command)
    {
        var handlerType =
            typeof(ICommandHandler<>).MakeGenericType(command.GetType());

        dynamic handler = _serviceProvider.GetService(handlerType);

        handler.Handle((dynamic)command);
    }
}

IServiceProvider will normally be supplied using Constructor Injection using your favorite IoC container. Using SimpleInjector, the registration of IServiceProvider will look like this:

Container.Register(typeof(IServiceProvider), () => Container);

That's it. All you need do henceforth is implement handlers for each of your commands. Like this:

public class ComputeResultCommandHandler : ICommandHandler<ComputeResultCommand>
{
    // NB: ComputeResultCommand class should implement ICommand marker interface
    public void Handle(ComputeResultCommand command) 
    {
        // Your code here
    }
}

Any good IoC container should be able to register open generics by scanning through assemblies. Thus, once configured appropriately, you do not need to explicitly register the implementations for the handler interface.

Now to something a little more interesting

The handler and processor above can be modified to return a value instead of void. That will basically transform the Command pattern used here to what is normally referred to as Query Object pattern. I'll still retain the name 'Command' and just show the modifications.

First, the marker interface becomes

public interface ICommand<TResult>
{
}

and the handler:

public interface ICommandHandler<TCommand, TResult> where TCommand : ICommand<TResult>
{
    TResult Handle(TCommand command);
}

The processor interface becomes (unsurprisingly):

public interface ICommandProcessor
{
    TResult Process<TResult>(ICommand<TResult> command);
}

and its implementation:

public sealed class CommandProcessor : ICommandProcessor
{
    private IServiceProvider _serviceProvider;
    public CommandProcessor(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public TResult Process(ICommand<TResult> command)
    {
        var handlerType =
            typeof(ICommandHandler<,>).MakeGenericType(command.GetType(), typeof(TResult));

        dynamic handler = _serviceProvider.GetService(handlerType);

        return handler.Handle((dynamic)command);
    }
}

That's it!

I will be interested in learning other solutions to this problem. Of course I googled it before honing my own solution. My search landed me to this StackOverflow question which was asked in 2010 and on MVC2. The provided solution suggested either implementing your own ActionDescriptor of writing a new ControllerFactory where some magic code was written to circumvent MVC's default behaviour since there were no MVC methods to override. Besides not being comfortable with messing around with the controller, I wasn't sure how things might have changed between MVC2 and MVC5.

I've tailored the thoughts in this blog post to answer the question there in case some other person runs into a similar problem.

That said, I'll love to hear your thoughts.