This project is read-only.

Support for interface based typed factories

Mar 14, 2013 at 10:10 PM
Edited Mar 14, 2013 at 10:11 PM
Hi,

Is support planned for interface based typed factories, like in Castle Windsor (http://docs.castleproject.org/Windsor.Typed-Factory-Facility-interface-based-factories.ashx)?

I'd love to be able to declare an IWhateverFactory interface and register it using syntax like 'container.RegisterFactory<IWhateverFactory>()', similar to Castle Windsor and Ninject.

The current API forces me to create a whole bunch of factory classes that all look pretty much identical. That's simple but not very DRY. Interface based typed factories keep the code simple but also DRY compatible.
Mar 14, 2013 at 11:51 PM
Simple Injector has no support for this and we won't have any plans to support this in the future. The reason is that it doesn't fit the chosen design strategy. Most important reason is that we try to keep the number of features that are not broadly applicable to a minimum to keep the API clean and manageable.

This feature does have its use, but the work around is simple: just create an implementation yourself. If you have an application design that contains many factories, you should take a step back and look closely at that design, since the use of many factories is not typical and might indicate that there is something missing in the design or the DI configuration. For instance, instead of using factories to control the lifetime of services, use one of the scoped lifestyles.

Still, RegisterFactory extension methods can be added fairly easy. The example below demonstrates this. Please note that the proper argument validation is missing. The "// Requires" comments mark those:
public static class SimpleInjectorFactoryExtensions
{
    public static void RegisterFactory<TFactory>(this Container container)
        where TFactory : class
    {
        // Requires: TFactory is interface
        // Requires: TFactory contains exactly one method
        // Requires: That method has no parameters.
        // Requires: That method does not return void

        Type returnType = typeof(TFactory).GetMethods().Single().ReturnType;
        Func<object> instanceCreator = () => container.GetInstance(returnType);
        var proxy = new FactoryProxy(typeof(TFactory), instanceCreator);
        container.RegisterSingle<TFactory>((TFactory)proxy.GetTransparentProxy());
    }

    public static void RegisterFactory<TFactory, T>(this Container container,
        Func<T, object> instanceCreator)
        where TFactory : class
    {
        // Requires: TFactory is interface
        // Requires: TFactory contains exactly one method
        // Requires: That method has exactly one parameter of type T.
        // Requires: That method does not return void

        var proxy = new FactoryProxy(typeof(TFactory), instanceCreator);
        container.RegisterSingle<TFactory>((TFactory)proxy.GetTransparentProxy());
    }

    public static void RegisterFactory<TFactory, T1, T2>(this Container container,
        Func<T1, T2, object> instanceCreator)
        where TFactory : class
    {
        // Requires: TFactory is interface
        // Requires: TFactory contains exactly one method
        // Requires: That method has exactly two parameter2 of type T1 and T2.
        // Requires: That method does not return void

        var proxy = new FactoryProxy(typeof(TFactory), instanceCreator);
        container.RegisterSingle<TFactory>((TFactory)proxy.GetTransparentProxy());
    }

    private sealed class FactoryProxy : RealProxy
    {
        private readonly Type interfaceToProxy;
        private readonly Delegate instanceCreator;

        public FactoryProxy(Type interfaceToProxy, Delegate instanceCreator)
            : base(interfaceToProxy)
        {
            this.interfaceToProxy = interfaceToProxy;
            this.instanceCreator = instanceCreator;
        }

        public override IMessage Invoke(IMessage msg)
        {
            var message = msg as IMethodCallMessage;

            return message != null ? this.InvokeMethodCall(message) : msg;
        }

        private IMessage InvokeMethodCall(IMethodCallMessage message)
        {
            if (message.MethodName == "GetType")
            {
                return new ReturnMessage(this.interfaceToProxy, null, 0, null, message);
            }

            var returnValue = this.instanceCreator.DynamicInvoke(message.Args);
            return new ReturnMessage(returnValue, null, 0, null, message);
        }
    }
}
Marked as answer by dot_NET_Junkie on 11/5/2013 at 7:37 AM
Mar 18, 2013 at 2:59 AM
Thanks so much for the extremely quick and comprehensive answer. I'm really enjoying the framework and it's nice to see that a lot of thought goes in to whether or not a feature is worth adding. Not much software is built with that outlook.