Search code examples
c#inversion-of-controlsimple-injector

Resolve handlers using Factory via IOC container


I have the task of refactoring a monolithic MVC application (1 controller action with over 1000 lines) into something more manageable.

The purpose of the program is to parse collections of different types, one by one, in a specific order.

Here is an example of a proposed 'better' solution...

public interface IFruit
{
}

public class Banana : IFruit
{
}

public class Apple : IFruit
{
}

public interface IFruitHandler<in TFruit> where TFruit : IFruit
{
    void Handle(IEnumerable<TFruit> fruit);
}

public class BananaHandler : IFruitHandler<Banana>
{
    public void Handle(IEnumerable<Banana> fruit)
    {
        Console.WriteLine("break apart the bunch of bananas");

        foreach (var banana in fruit)
        {
            Console.WriteLine("Peel the banana");
        }
    }
}

public class AppleHandler : IFruitHandler<Apple>
{
    public void Handle(IEnumerable<Apple> fruit)
    {
        foreach (var apple in fruit)
        {
            Console.WriteLine("Slice the apple");
        }

        Console.WriteLine("Throw the apple cores away");
    }
}

As you can see, each fruit has its own handler, and each handler implementation may differ slightly. Some handlers do stuff before working through the collection, others do things afterward, some do things before AND after.

The application presents one object (via an MVC controller) with all the different fruit collections as properties (a big view model basically). I would like some kind of mediator that can take all of these collections and pass them to the appropriate handler.

This is what i had in mind...

public class Mediator : IMediator
{
    private readonly Func<Type, IFruitHandler<IFruit>> handlerFactory;

    public Mediator(Func<Type, IFruitHandler<IFruit>> handlerFactory)
    {
        this.handlerFactory = handlerFactory;
    }

    public void Execute(IEnumerable<IEnumerable<IFruit>> fruitBundles)
    {
        foreach (var bundle in fruitBundles)
        {
            if (!bundle.Any())
                continue;

            var handler = handlerFactory(bundle.First().GetType());
            handler.Handle(bundle);
        }
    }
}

A Fruit bundle will always contain only one fruit, and the bundles will be in a specific order. The signature for Mediator.Execute is loose enough for one bundle to contain different fruits. This should not happen.

The question is two-fold....

I can register all FruitHandlers with OpenGenerics...

container.RegisterManyForOpenGeneric(typeof(IFruitHandler<>),typeof(IFruitHandler<>).Assembly);

but I am having difficulties getting the registration correct for the factory - how do i register the factory with Simple Injector?

The Mediator / handler factory implementation feels awkward, as does the IFruit interface (its effectively just a marker) - would you change the design?


Solution

  • You can implement the Mediator in the following way but it has some downsides:

    • it uses dynamic
    • the Mediator needs to reference the Container (although this is ok if you define this class in your composition root)

    I feel the question is too general to be able to provide any specific advice on a better way to design your solution.

    class Mediator : IMediator
    {
        private readonly Container container;
    
        public Mediator(Container container)
        {
            this.container = container;
        }
    
        public void Handle(IEnumerable<IEnumerable<IFruit>> fruitBundles)
        {
            foreach (var bundle in fruitBundles)
            {
                if (bundle.Any())
                {
                    dynamic instance = bundle.First();
                    this.Handle(instance, bundle);
                }
            }
        }
    
        private void Handle<T>(T instance, IEnumerable<IFruit> bundle)
            where T : IFruit
        {
            var handler = this.container.GetInstance<IFruitHandler<T>>();
    
            handler.Handle(bundle.Cast<T>());
        }
    }