Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unity with factory method who need parameters

I want to register a type in an Unity container with a factory method who needs paramters. These parameters are to be resolved by unity but only at runtime.

Factory method code :

public static IApp Create(IOne, ITwo) {...}

Register code :

container.RegisterType(typeof(IApp), new InjectionFactory(f => App.Create(???, ???)));

What do I need to replace the '???' ?

More informations :

My Unity configuration is made in two phases. First phase (application is starting), I register all my object but one :

container.RegisterType<IOne, One>();
container.RegisterType<ITwo, Two>();
container.RegisterType<IApp, App>();
// ...

Phase two (user is logging), I juste register an instance of a context object who is used in constructor of all my classes (One, Two, App, ...) :

var childContainer = container.CreateChildContainer();
childContainer.RegisterInstance<AppEnvironment>(new AppEnvironment(userName));

This is my code without using InjectionFactory. It works fine. Now, I have to register multiple times my IApp interface, with calling a different static method each time. Example of static method :

public static IApp Create(IOne one, ITwo two, AppEnvironment env) 
{
    _one = one;
    _two = two;
    _env = env;
}

If I register IApp this this code :

container.Register(typeof(IApp), new InjectionFactory(f => App.Create(container.Resolve<IOne>(), container.Resolve<ITwo>(), container.Resolve<AppEnvironment>()));

Then my env variable is not set.

But if I register my env instance (just for test purpose, I can't do that in my case), it works.

like image 649
Olof Avatar asked Apr 13 '17 11:04

Olof


Video Answer


3 Answers

Finally got the solution. It looks like willys one :

container.RegisterType<IApp, App>(
    new InjectionFactory(
        f => App.Create(
            f.Resolve<IOne>(), 
            f.Resolve<ITwo>()
        )
    )
);
like image 160
Olof Avatar answered Oct 19 '22 23:10

Olof


Try

container.RegisterType(
    typeof(IApp),
    new InjectionFactory(f => App.Create(
        container.Resolve<IOne>(), 
        container.Resolve<ITwo>())));
like image 40
Sergej Christoforov Avatar answered Oct 20 '22 00:10

Sergej Christoforov


Let's say you have this Interface/Class :

public interface IApp { }
public class App : IApp
{
    public App(IOne iOneInstance, ITwo iTwoInstance) { /* Irrelevant */}
}

public interface IOne { }
public class OneA : IOne { }
public class OneB : IOne { }
public interface ITwo { }
public class TwoA : ITwo { }
public class TwoB : ITwo { }

Let's make that your class with the factory method. You wouldn't use both of these, only one version :

// Version One
public class ClassWithFactoryMethodOne
{
    Func<IOne, ITwo, IApp> iAppFactory;
    public ClassWithFactoryMethodOne(Func<IOne, ITwo, IApp> iAppFactory)
    {
        this.iAppFactory = iAppFactory;
    }

    public IApp Create(IOne iOneInstance, ITwo iTwoInstance)
    {
        return this.iAppFactory(iOneInstance, iTwoInstance);
    }
}

// Version Two
public class ClassWithFactoryMethodTwo
{
    Func<string, string, IApp> iAppFactory;
    ClassWithFactoryMethodTwo(Func<string, string, IApp> iAppFactory)
    {
        this.iAppFactory = iAppFactory;
    }

    public IApp Create(string iOneNamedRegistration, string iTwoNamedRegistration)
    {
        return this.iAppFactory(iOneNamedRegistration, iTwoNamedRegistration);
    }
}

If you do the registrations like this, it should work :

class Program
{
    void Main()
    {
        IUnityContainer container = new UnityContainer();

        container.RegisterType<IOne, OneA>("A");
        container.RegisterType<IOne, OneB>("B");
        container.RegisterType<ITwo, TwoA>("A");
        container.RegisterType<ITwo, TwoB>("B");

        container.RegisterType<Func<IOne, ITwo, IApp>>(
            new InjectionFactory(c =>
                new Func<IOne, ITwo, IApp>((iOne, iTwo) =>
                    c.Resolve<IApp>(
                        new ParameterOverride("iOneInstance", iOne),
                        new ParameterOverride("iTwoInstance", iTwo)))));

        container.RegisterType<Func<string, string, IApp>>(
            new InjectionFactory(c =>
                new Func<string, string, IApp>((iOneNamedRegistration, iTwoNamedRegistration) =>
                    c.Resolve<IApp>(
                        new ParameterOverride("iOneInstance", c.Resolve<IOne>(iOneNamedRegistration)),
                        new ParameterOverride("iTwoInstance", c.Resolve<ITwo>(iTwoNamedRegistration))))));

        // Alternate writing
        container.RegisterType<Func<string, string, IApp>>(
            new InjectionFactory(c =>
                new Func<string, string, IApp>((iOneNamedRegistration, iTwoNamedRegistration) =>
                {
                    IOne iOne = c.Resolve<IOne>(iOneNamedRegistration);
                    ITwo iTwo = c.Resolve<ITwo>(iTwoNamedRegistration);

                    IApp iApp = c.Resolve<IApp>(
                        new ParameterOverride("iOneInstance", iOne),
                        new ParameterOverride("iTwoInstance", iTwo));

                    return iApp;
                })));

        ClassWithFactoryMethodOne versionOne = container.Resolve<ClassWithFactoryMethodOne>();
        // Somewhere you have logic and end up with instances of IOne and ITwo then you :
        IApp iApp1 = versionOne.Create(iOneInstance, iTwoInstance); // This doesn't compile cause you'd need the instances.

        ClassWithFactoryMethodTwo versionTwo = container.Resolve<ClassWithFactoryMethodTwo>();
        IApp iApp2 = versionTwo.Create("A", "B");
    }
}

I think that if I explain the // Alternate Writing I wrote in the Main method, it will clear up some things :

When you use new InjectionFactory, you write a lambda expression that will receive an IUnityContainer as parameter (which I named c.) That expression is used to create the factory itself. In the factory, which has access to c, I then IOne iOne = c.Resolve<IOne>(iOneNamedRegistration);, hence asking the container to resolve an IOne, using the first named registration that the factory will have received. The same thing for ITwo. Then, I ask the container to resolve an IApp for me, overriding the parameter named iOneInstance with the instance of IOne, and same for ITwo.

Oh, and the last lines in the method would be just how you'd actually call the Create methods. The 2nd one would ask the instance of ClassWithFactoryMethodTwo to create an IApp with an IOne that is actually a OneA, and an ITwo that is actually a TwoB.

Anything left unclear?

like image 40
Tipx Avatar answered Oct 20 '22 01:10

Tipx