Unity auto-factory with params
Asked Answered
C

4

19

I'm trying to figure out the correct way to inject an auto-factory which takes params, or even if this is possible with Unity.

For example I know I can do this:

public class TestLog
{
     private Func<ILog> logFactory;

     public TestLog(Func<ILog> logFactory)
     {
          this.logFactory = logFactory;
     }
     public ILog CreateLog()
     {
         return logFactory();
     }
}

Container.RegisterType<ILog, Log>();
TestLog test = Container.Resolve<TestLog>();
ILog log = test.CreateLog();

Now what I'll like to be able to do is:

public class TestLog
{
     private Func<string, ILog> logFactory;

     public TestLog(Func<string, ILog> logFactory)
     {
          this.logFactory = logFactory;
     }
     public ILog CreateLog(string name)
     {
         return logFactory(name);
     }
}

Container.RegisterType<ILog, Log>();
TestLog test = Container.Resolve<TestLog>();
ILog log = test.CreateLog("Test Name");

Unfortunately this doesn't work. I can see how you can set up custom factories for creating instances in Unity, just can't seem to fund any clear examples for this example.

Obviously I could create my own factory but I'm looking for an elegant way to do this in Unity and with minimum code.

Colic answered 29/9, 2010 at 19:45 Comment(0)
C
34

Sorry to be one of those annoying people who answer their own questions but I figured it out.

public class TestLog
{
    private Func<string, ILog> logFactory;

    public TestLog(Func<string, ILog> logFactory)
    {
         this.logFactory = logFactory;
    }
    public ILog CreateLog(string name)
    {
        return logFactory(name);
    }
}

Container.RegisterType<Func<string, ILog>>(
     new InjectionFactory(c => 
        new Func<string, ILog>(name => new Log(name))
     ));

TestLog test = Container.Resolve<TestLog>();
ILog log = test.CreateLog("Test Name");
Colic answered 29/9, 2010 at 20:22 Comment(4)
Is it possible to do something like that, but using constructor injection?Sterilant
@Chesheersky the example above uses constructor injection.Colic
I don't find "answering your own question" to be annoying, especially when it is useful :) Could you please explain this RegisterType<Func<string, ILog>>(new InjectionFactory( ..) code? It's not that obvious... I got it that it ensures that test.CreateLog("dsffads") is resolved, but how?, why?Disbranch
The InjectionFactory method is how you register a lambda expression with Unity which defines how to construct the instance of type Func<string, ILog>. The lambda function is an anonymous function that constructs ILog from a given string.Colic
F
8

The answer by @TheCodeKing works fine, but in most (possibly all?) cases could be shortened to the following:

Container.RegisterInstance<Func<string, ILog>>(name => new Log(name));

(note that I'm using RegisterInstance() instead of RegisterType())

Since the Func<> implementation is already a kind of factory there's usually no need to wrap it in a InjectionFactory. It only ensures that each resolution of the Func<string, ILog> is a new instance, and I can't really think of a scenario that requires this.

Funches answered 12/5, 2016 at 12:23 Comment(5)
The scenario is sufficient if function implementation does not need container instance. If you have more possibilities of dependency resolution you can choose which one is correct by the InjectionFactory calling c.Resolve<IDependency>(name) where c is the container parameter. In RegisterInstance the container is not accessible as an argument.Stelliform
Hey @PiotrKasprzyk sorry for a shameless plug but I implemented a Unity extension that can, basically, take name from the factory-func's parameter and pass it to the constructor of Log, but resolve IDependency from the container (and pass it to the constructor too). Please take a look at my answer for details.Palindrome
@PiotrKasprzyk why just not capture Container instance from lambda? No need of container parameter whis way.Hassock
@Ghosthack I often do that, but you'll run into some LifetimeManager problems. For example, if you use a child container to resolve down the line, you'll still use the parent container that was capture at registration time.Prejudicial
@Prejudicial Agree, this is an issue. Thanks for your note.Hassock
P
2

Autofac has parameterized instantiation to handle scenarios which need an auto-factory with parameters.

While Unity doesn't support this out of the box, it's possible to implement an extension which will work in a way similar to Autofac's.

A shameless plug: I implemented such an extension -- Parameterized Auto Factory.
It can be used in a way similar to this:

public class Log
{
    public void Info(string info) { /* ... */ }
    public void Warn(string info) { /* ... */ }
    public void Error(string info) { /* ... */ }
}

public class LogConsumer
{
    private readonly Log _log;
    private readonly string _consumerName;

    public LogConsumer(Log log, string consumerName)
    {
        _log = log;
        _consumerName = consumerName;
    }

    public void Frobnicate()
        => _log.Info($"{nameof(Frobnicate)} called on {_consumerName}");
}

var container = new UnityContainer()
    .AddNewExtension<UnityParameterizedAutoFactoryExtension>();

var logConsumerFactory = container.Resolve<Func<string, LogConsumer>>();
var gadget = logConsumerFactory("Gadget");
gadget.Frobnicate();

Please notice:

  • Func<string, LogConsumer> is resolved from container, but it wasn't registered anywhere -- it's generated automatically.
  • Func<string, LogConsumer> only provides the string consumerName parameter to LogConsumer's constructor. As a result, the Log log parameter is resolved from the container. If the auto-factory func looked like this Func<string, Log, LogConsumer> instead, then all the parameters of LogConsumer's constructor would've been supplied through the auto-factory.

Basically, the extension works like this:

  1. It registers a so called BuilderStrategy in Unity's pipeline.
  2. This strategy gets invoked whenever Unity is going to build a type's instance.
  3. If the type to be built hasn't been registered explicitly and is a Func with parameters, the extension intercepts the instance building process.
  4. Now we only need to dynamically create a Funcof the given type which resolves its return type from the container and passes the Func's parameters as a ResolverOverride collection to the IUnityContainer.Resolve method.
Palindrome answered 31/5, 2018 at 1:5 Comment(0)
T
1

In case you're looking for a fully typed factory interface (allowing for XML documentation and parameter names, for instance), you could use a NuGet package I created, which you can leverage simply by defining an interface for the factory, and then associating it with the concrete type you want to instantiate.

Code lives in GitHub: https://github.com/PombeirP/FactoryGenerator

Thrive answered 23/2, 2013 at 21:13 Comment(3)
Exactly what I need. Suggest you to add an example to your answer like here: blog.ploeh.dk/2012/03/15/ImplementinganAbstractFactory in Dynamic Proxy section.Hassock
@Ghosthack I'd appreciate if you check out github.com/mykolav/unitycontainer-param-autofactory I believe its advantage over FactoryGenerator is it doesn't require any code generation step. I. e., it generates factories dynamically in run-time. And doesn't even require to register a factory in the container. Please see my answer for details.Palindrome
@Nik Sure, looks awesome. Sorry, I've seen your comment just now. Soon I will need to replace default IoC of MvvmCross to Unity in a new project. I will try your extension for sure and provide some feedback. Thank you very much for it.Hassock

© 2022 - 2024 — McMap. All rights reserved.