Question

I'm a Ninject user that try to learn Simple Injector

One Ninject feture that I often use in my applications is the Factory Interface

With that I can create a Interface like this:

public interface IBarFactory
{
   Bar CreateBar();
}

And the register it like this

kernel.Bind<IBarFactory>().ToFactory();

Then I simple can use IBarFactory, and don't have to create a implementation of IBarFactory

I now try to find anything similar in Simple njector, and have found this. But with that approacher, I have to implement the factory interface (more code). And how do I do if the Bar object need a reference to another object?

Was it helpful?

Solution

Simple Injector lacks this a factory interface facility. The idea behind this omission is that when applying Dependency Injection correctly, the need for using factories is minimized, which makes the usefulness of such feature limited.

In Simple Injector you have to write an implementation yourself, but this is usually trivial. Example:

private sealed class SimpleInjectorBarFactory : IBarFactory
{
    private readonly Container container; 
    public SimpleInjectorBarFactory(Container container) => this.container = container;
    public Bar CreateBar() => this.container.GetInstance<Bar>();
}

This class can be registered like this:

container.RegisterSingleton<IBarFactory, SimpleInjectorBarFactory>();

Or -if you're lazy- you can register a Func<Bar> to be injected as follows:

container.RegisterInstance<Func<Bar>>(() => container.GetInstance<Bar>());

Note that since this SimpleInjectorBarFactory implementation depends on the Container instance, it should be part of the Composition Root to prevent using the Container as a Service Locator. By placing the classes inside your Composition Root it becomes merely a piece of infrastructure.

So the feature is excluded deliberately, but the library can be extended by making use of the ResolveUnregisteredType event:

using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;

public static class AutomaticFactoryExtensions {
    public static void RegisterFactory<TFactory>(this Container container) {
        if (!typeof(TFactory).IsInterface)
            throw new ArgumentException(typeof(TFactory).Name + " is no interface");

        container.ResolveUnregisteredType += (s, e) => {
            if (e.UnregisteredServiceType == typeof(TFactory)) {
                e.Register(Expression.Constant(
                    value: CreateFactory(typeof(TFactory), container),
                    type: typeof(TFactory)));
            }
        };
    }

    private static object CreateFactory(Type factoryType, Container container) {
        var proxy = new AutomaticFactoryProxy(factoryType, container);
        return proxy.GetTransparentProxy();
    }

    private sealed class AutomaticFactoryProxy : RealProxy {
        private readonly Type factoryType;
        private readonly Container container;

        public AutomaticFactoryProxy(Type factoryType, Container container)
            : base(factoryType) {
            this.factoryType = factoryType;
            this.container = container;
        }

        public override IMessage Invoke(IMessage msg) {
            if (msg is IMethodCallMessage) {
                return this.InvokeFactory(msg as IMethodCallMessage);
            }

            return msg;
        }

        private IMessage InvokeFactory(IMethodCallMessage msg) {
            if (msg.MethodName == "GetType")
                return new ReturnMessage(this.factoryType, null, 0, null, msg);

            if (msg.MethodName == "ToString")
                return new ReturnMessage(this.factoryType.Name, null, 0, null, msg);

            var method = (MethodInfo)msg.MethodBase;
            object instance = this.container.GetInstance(method.ReturnType);
            return new ReturnMessage(instance, null, 0, null, msg);
        }
    }
}

Using the extension method above, you can do the registration for the factory in a way very similar to Ninject's registration:

container.RegisterFactory<IBarFactory>();

That's it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top