Search code examples
c#dependency-injectionninjectfactory-pattern

Binding factory interfaces to factory, except for those that have an implementation?


I have some factory interfaces.

  • ICustomerManagementPresenterFactory
  • ICustomerDetailPresenterFactory

Some factories don't need any implementor, so I can bind them as follows.

IKernel kernel = new StandardKernel();
kernel.Bind(services => services
    .From(AppDomain.CurrentDomain
        .GetAssemblies()
        .Where(a => a.FullName.Contains("MyProject")
                 && !a.FullName.Contains("Tests")))
    .SelectAllInterfaces()
    .EndwingWith("Factory")
    .BindToFactory());

Which works flawlessly as long I don't need to provide arguments to the constructor, which should be provided through Method Injection.

Besides, using this code, I have bound my ICustomerManagementPresenterFactory as well, and it is not bound to its implementer.

ICustomerManagementPresenterFactory

public interface ICustomerManagementPresenterFactory { 
    CustomerManagementPresenter Create();
}

CustomerManagementPresenterFactory

public class CustomerManagementPresenterFactory : ICustomerManagementPresenterFactory {
    public CustomerManagementPresenterFactory(ICustomerManagementView view
                                            , ICustomerDetailPresenterFactory factory) {
        this.factory = factory; 
        this.view = view;
    }

    public CustomerManagementPresenter Create() {
        return new CustomerManagementPresenter(view, factory);
    }

    private readonly ICustomerDetailPresenterfactory factory;
    private readonly ICustomerManagementView view;
}

So, because the constructor of CustomerManagementPresenter takes two arguments, I wish to implement a factory which won't need to be method injected the dependencies of the class it creates, and I keep using Constructor Injection.

So, I would like to benefit from the Convention Binding, and still bind the two differently.

How might I go about this?


Solution

  • Sadly enough you can't retrieve the list of types "selected" by the convention. That means you'll have to work around it someway.

    The syntax offers the Where(Func<Type, bool> selector) and the Excluding(IEnumerable<Type> types) methods. So you'd need to get the interface of all implemented factories before binding the interface factories with .ToFactory(). For example:

    IKernel kernel = new StandardKernel();
    
    IList<Type> implementedFactoryInterfaces = new List<Type>();
    kernel.Bind(services => services
        .From(AppDomain.CurrentDomain
            .GetAssemblies()
            .Where(a => a.FullName.Contains("MyProject")
                        && !a.FullName.Contains("Tests")))
        .SelectAllClasses()
        .EndingWith("Factory")
        .Where(classFactoryType =>
        {
            implementedFactoryInterfaces.Add(classFactoryType.GetInterfaces().Single());
            return true;
        })
        .BindDefaultInterface());
    
    
    kernel.Bind(services => services
        .From(AppDomain.CurrentDomain
            .GetAssemblies()
            .Where(a => a.FullName.Contains("MyProject")
                        && !a.FullName.Contains("Tests")))
        .SelectAllInterfaces()
        .EndingWith("Factory")
        .Excluding(implementedFactoryInterfaces)
        .BindToFactory());
    

    An alternative would be to implement a binding generator which check whether there's an implementation for the passed interface-Type and create the binding accordingly:

    public class InterfaceAndClassFactoryBindingGenerator : IBindingGenerator
    {
        public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
        {
            if (!type.IsInterface)
            {
                throw new ArgumentOutOfRangeException("type", type, "is not an interface, but only interfaces are supported");
            }
    
            Type classImplementingTheFactoryInterface = type.Assembly.GetTypes()
                .Where(t => t.IsClass)
                .SingleOrDefault(type.IsAssignableFrom);
    
            if (classImplementingTheFactoryInterface == null)
            {
                bindingRoot.Bind(type).ToFactory();
            }
            else
            {
                bindingRoot.Bind(type).To(classImplementingTheFactoryInterface);
            }
        }
    }
    
    
    IKernel kernel = new StandardKernel();
    kernel.Bind(services => services
        .From(AppDomain.CurrentDomain
            .GetAssemblies()
            .Where(a => a.FullName.Contains("MyProject")
                        && !a.FullName.Contains("Tests")))
        .SelectAllInterfaces()
        .EndingWith("Factory")
        .BindWith<InterfaceAndClassFactoryBindingGenerator>());