Method Injection with Simple Injector

In this post I will outline a trick I created while experimenting with the code for my post Managing Commands and Queries. If you are unfamiliar with these two patterns then please see these posts here and here for a great introduction. For the remainder of this post I will assume you have fully subscribed to the idea of parameter objects and are comfortable with a variation of the following two abstractions.

Command

Actions that change something and return nothing

public interface ICommand { }

public interface ICommandHandler<TCommand> where TCommand : ICommand
{
    void Run(TCommand command);
}

Query

Actions that return something and change nothing

public interface IQuery<TResult> { }

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

Please note I do not use this technique myself!

just because you can, doesn’t mean you should!

NOTE: Steven (The man who created Simple Injector) has told me that this is really not a good idea as Simple Injector was not designed to support this feature.

I am documenting this here because I’d love to get some feedback on it.

As I worked through the details of my last post I got to thinking that wouldn’t it be great to be able to completely get rid of the .Execute when calling injected logic?

This is ok

public class SomeController
{
    private readonly IQueryHandler<GetProductDetails, IEnumerable<ProductDetail>> getProductDetails;

    public SomeController(IQueryHandler<GetProductDetails, IEnumerable<ProductDetail>> getProductDetails)
    {
        this.getProductDetails = getProductDetails;
    }

    public IEnumerable<ProductDetail> Index()
    {
        return this.getProductDetails.Execute(new GetProductDetails(ProductType.Investment));
    }
}

But this is better

public class SomeController
{
    private readonly Func<GetProductDetails, IEnumerable<ProductDetail>> getProductDetails;

    public SomeController(Func<GetProductDetails, IEnumerable<ProductDetail>> getProductDetails)
    {
        this.getProductDetails = getProductDetails;
    }

    public IEnumerable<ProductDetail> Index()
    {
        return this.getProductDetails(new GetProductDetails(ProductType.Investment));
    }
}

I found a way to get this to work with Simple Injector. The method injection is done by registering a new handler for the Container‘s ResolveUnregisteredType event that will find the relevant command or query handler and inject a delegate to the Execute method of an instance of the resolved handler.

With the following code, whenever you need a ICommandHandler<> you declare an Action<> in the constructor and for IQueryHandler<,> you declare a Func<,>.

And all it takes is this one line of code to set up the registration plus the extension method at the bottom of this post

container.RegisterCommandAndQueryHandlersAsDelegates()

Steven raised a couple of good points (doesn’t he always?)

Take for instance at the following delegate:

Action<MoveCustomerCommand>

What does this represent? Does this represent an ICommandHandler<MoveCustomerCommand>?

Or does it represent an IValidator<MoveCustomerCommand>?

Or perhaps an ISecurityValidator<MoveCustomerCommand>?

Or perhaps even an IFetchingStrategy<MoveCustomerCommand>?

This information is completely lost. This might not be a problem in case you only present queries and commands as Funcs and Actions, but once you start doing that, you soon want to use Funcs and Actions everywhere. And that’s when you will start to get those ambiguity conflicts. And these are very hard to solve. In OOP we really need our interfaces; I don’t really see an option around this.

Here’s the code ….

public static class CommandQueryDelegateExtensions
{
    public static void RegisterCommandAndQueryHandlersAsDelegates(this Container container)
    {
        var commandDelegateBuilder = new CommandDelegateBuilder(container);
        var queryDelegateBuilder = new QueryDelegateBuilder(container);

        container.ResolveUnregisteredType += (sender, e) =>
        {
            if (commandDelegateBuilder.CanCreate(e.UnregisteredServiceType))
            {
                e.Register(commandDelegateBuilder.Create(e.UnregisteredServiceType));
            }
            else if (queryDelegateBuilder.CanCreate(e.UnregisteredServiceType))
            {
                e.Register(queryDelegateBuilder.Create(e.UnregisteredServiceType));
            }
        };
    }

    private class CommandDelegateBuilder
    {
        private readonly Container container;

        public CommandDelegateBuilder(Container container)
        {
            this.container = container;
        }

        public bool CanCreate(Type type)
        {
            return type.IsGenericType &&
                type.GetGenericTypeDefinition() == typeof(Action<>) &&
                typeof(ICommand)
                    .IsAssignableFrom(type.GetGenericArguments()[0]);
        }

        public Registration Create(Type type)
        {
            var method = (
                from m in this.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
                where m.Name == "Create"
                select m).Single();

            return (Registration)method
                .MakeGenericMethod(type.GetGenericArguments())
                .Invoke(this, null);
        }

        private Registration Create<TCommand>() where TCommand : ICommand
        {
            var registration = Lifestyle.Singleton.CreateRegistration<Action<TCommand>>(
                () => this.Run<TCommand>,
                this.container);

            return registration;
        }

        private void Run<TCommand>(TCommand param) where TCommand : ICommand
        {
            var handler = this.container.GetInstance<ICommandHandler<TCommand>>();

            handler.Run(param);
        }
    }

    private class QueryDelegateBuilder
    {
        private readonly Container container;

        public QueryDelegateBuilder(Container container)
        {
            this.container = container;
        }

        public bool CanCreate(Type type)
        {
            return type.IsGenericType &&
                type.GetGenericTypeDefinition() == typeof(Func<,>) &&
                typeof(IQuery<>).MakeGenericType(type.GetGenericArguments()[1])
                    .IsAssignableFrom(type.GetGenericArguments()[0]);
        }

        public Registration Create(Type type)
        {
            var method = (
                from m in this.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
                where m.Name == "Create"
                select m).Single();

            return (Registration)method
                .MakeGenericMethod(type.GetGenericArguments())
                .Invoke(this, null);
        }

        private Registration Create<TQuery, TResult>() where TQuery : IQuery<TResult>
        {
            var registration = Lifestyle.Singleton.CreateRegistration<Func<TQuery, TResult>>(
                () => this.Execute<TQuery, TResult>,
                this.container);

            return registration;
        }

        private TResult Execute<TQuery, TResult>(TQuery param) where TQuery : IQuery<TResult>
        {
            var handler = this.container.GetInstance<IQueryHandler<TQuery, TResult>>();

            return handler.Execute(param);
        }
    }
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.