Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to register a class that has `Func<>` as parameter?

I have the following code:

_container = new UnityContainer();
_container.RegisterType<IDownloader, Downloader>();
_container.RegisterType<INewObject, NewObject>();
_container.RegisterType<SearchViewModel>();

SearchViewModel class with constructor injection:

class SearchViewModel
{
    private readonly Func<IDownloader> _downloaderFactory;
    private readonly INewObject _newObject;
    private IDownloader _downloader;

    public SearchViewModel(Func<IDownloader> downloaderFactory, INewObject newObject)
    {
        _downloaderFactory = downloaderFactory;
        _newObject = newObject;
    }        
}

The question: How to register SearchViewModel that has Fun<> as parameter?

_container.RegisterType<SearchViewModel>(new InjectionConstructor(DownloaderFactory()));

The code above works only without INewObject.

The goal: Resolve factory with InjectionConstructor and resolve INewObject, INewObject2, INewObject3 automatically (like without parameters: RegisterType<SearchViewModel>()).

Is it possible? Maybe alternates?

like image 224
Ievgen Martynov Avatar asked Mar 22 '12 12:03

Ievgen Martynov


2 Answers

I have solved the problem:

_container.RegisterType<Func<IDownloader>>(new InjectionFactory(i => 
            new Func<IDownloader> (() => _container.Resolve<IDownloader>())));
_container.RegisterType<SearchViewModel>();

new Func is a key, because before I tried:

_container.RegisterType<Func<IDownloader>>(new InjectionFactory(i => 
            _container.Resolve<IDownloader>()));

Also the better way to use IDownloaderFactory instead of Func<IDownloader> downloaderFactory. IDownloaderFactory can encapsulate the delegate.

Also I think that using a delegate as dependency inside factory is better solution than broken Composition Root.

like image 129
Ievgen Martynov Avatar answered Nov 01 '22 04:11

Ievgen Martynov


The generally accepted pattern to use here is to declare an abstract factory and make it slightly more explicit:

 public interface IDownloaderFactory
 {
    IDownloader Create();
 }

Then you create a class to represent the factory that simply uses the container again to resolve instances:

 public class DownloaderFactory : IDownloaderFactory
 {
     private UnityContainer _Container;
     public DownloaderFactory(UnityContainer container)
     {
         this._Container = container;
     }

     public IDownloader Create()
     {
         return this._Container.Resolve<IDownloader>();
     }
 }

Using this approach is more explicit and plays more nicely with the containers, also it still keeps the container away from your application and business logic, now you just need a small adjustment to your SearchViewModel class:

class SearchViewModel
{
   private readonly IDownloaderFactory _downloaderFactory;
   private readonly INewObject _newObject;

   public SearchViewModel(IDownloaderFactory downloaderFactory, INewObject newObject)
   {
       _downloaderFactory = downloaderFactory;
       _newObject = newObject;

       Console.WriteLine(downloaderFactory.Create().GetHashCode());
       Console.WriteLine(downloaderFactory.Create().GetHashCode());
   }

}

Now you can see it just works and creates new instances each time.

Setting up the container would look like this:

        var container = new UnityContainer();
        container.RegisterType<IDownloader, Downloader>();
        container.RegisterType<INewObject, NewObject>();
        container.RegisterType<IDownloaderFactory, DownloaderFactory>();
        container.RegisterType<SearchViewModel>();
        container.RegisterInstance(container);
        var model = container.Resolve<SearchViewModel>();

Notice that you need to register the instance of the container you are working with so that the factory gets the same instance either using this method or a ThreadLocal instancing or something.

Note: also just be wary of the fact that using the Func approach or using the container to resolve the downloader may cause undesired effects in your client. For instance if the container is set up by default to be transient to objects of Downloader then a new instance is created each time. Chaning the lifetime on the container may result in the client to get the same instance each time. In such a case it is better to manually construct the downloader object in the factory and use the container only for the arguments of downloader.

like image 35
Andre Avatar answered Nov 01 '22 02:11

Andre