SeanKilleen
SeanKilleen

Reputation: 8977

How do I wire an IoC Container to pass a value to a factory method to resolve?

Background / Goal

Question

Example Code

Normally we're doing something like the following coming in:

public class MyService
{   DependentObject _dependentObject;

    public MyService(int clientSiteID)
    {
       // ...
       _dependentObject = new dependentObjectFactory(clientSiteID).GetDependentObject();

    }

    public void DoAThing()
    {    
         //...
         _dependentObject.DoSomething(); 
    }

}

What I'd like to do:

public class MyService
{   DependentObject _dependentObject;

    public MyService(int clientSiteID)
    {
       // ...
       _dependentObject = MyTypeResolver.GetWIthClientContext<IDependentObject>(clientSiteID);
    }

    public MyService(int clientSiteID, IDependentObject dependentObject)
    {
       // ...
       _dependentObject = dependentObject;

    }

    public void DoAThing()
    {    
         //...
         _dependentObject.DoSomething(); 
    }

}

I would set up the IoC container in such a way that I can use my MyTypeResolver to pass in the clientSiteID, and have the container call my DependentObjectFactory and return the correct object result.

I'm new to IoC containers, and while I'm trying to plow through the literature, I have the feeling it may be easier than I'm making it so I'm asking here.

Upvotes: 0

Views: 127

Answers (1)

Stephen Byrne
Stephen Byrne

Reputation: 7475

Probably the simplest way to do this is to use an Abstract Factory. Most IOC frameworks can auto-create them for you, but here's how you can do it manually (I always prefer to do it manually first so I know it works, and then you can check out how the framework can help you automagic it)

Now one thing to mention - I would recommend a slight readjustment of how the final solution works, but I'll go into that once I have shown how it can currently work. Example below assumes Ninject and please excuse any typos, etc.

First create an interface for your dependency

public interface IDependentObject
{
    void DoSomething();
}

Then declare empty marker interfaces for each specific implementation of IDependentObject

public interface INormalDependentObject:IDependentObject{};
public interface ISpecialDependentObject:IDependentObject{}

and implement them:

public class NormalDependentObject:INormalDependentObject
{
    readonly int _clientID;
    public DependentObject(int clientID)
    {
       _clientID=clientID;
    }
    public void DoSomething(){//do something}
}

public class DependentObject:ISpecialDependentObject
{
    readonly int _clientID;
    public DependentObject(int clientID)
    {
       _clientID=clientID;
    }
    public void DoSomething(){//do something really special}
}

and of course as you mentioned you may have many more implementations of IDependentObject.

There may be a more elegant way of allowing your IOC framework to resolve at runtime without having to declare the marker interfaces; but for now I find it useful to use them as it makes the binding declarations easy to read :)

Next, declare an interface and implementation of an IDependentObjectFactory:

public interface IDependentObjectFactory
{
   IDependentObject GetDependenObject(int clientID);
}

public class DependentObjectFactory: IDependentObjectFactory
{
    readonly _kernel kernel;
    public DependentObjectFactory(IKernel kernel)
    {
        _kernel=kernel;
    }

    public IDependentObject GetDependenObject(int clientID)
    {
          //use whatever logic here to decide what specific IDependentObject you need to use.
          if (clientID==100)
          {
              return _kernel.Get<ISpecialDependantObject>(
                     new ConstructorArgument("clientID", clientID));
          }
          else
          {
             return _kernel.Get<INormalDependentObject>(
                     new ConstructorArgument("clientID", clientID));
          }
    }    
}

Wire these up in your Composition Root:

_kernel.Bind<INormalDependentObject>().To<NormalDependentObject>();
_kernel.Bind<ISpecialDependentObject>().To<SpecialDependentObject>();
_kernel.Bind<IDependentObjectFactory>().To<DependentObjectFactory>();

and finally inject your factory into the service class:

public class MyService
{   
    IDependentObject _dependentObject;
    readonly IDependentObjectFactory _factory;    

    //in general, when using DI, you should only have a single constructor on your injectable classes. Otherwise, you are at the mercy of the framework as to which signature it will pick if there is ever any ambiguity; most all of the common frameworks will make different decisions!
    public MyService(IDependentObjectFactory factory)
    {
       _factory=factory;
    }

    public void DoAThing(int clientID)
    {    
         var dependent  _factory.GetDependentObject(clientID);
         dependent.DoSomething();
    }
}

Suggested changes

  • One immediate change from your structure above is that I have left clientID out of the service constructor and moved it to a method argument of DoAThing; this is because it makes a bit more sense to me that the Service itself would be stateless; of course depending on your scenario, you may want to not do that.

I mentioned that I had a slight adjustment to suggest , and it's this; the solution above depends (no pun!) on implementations of IDependentObject having a constructor with this signature:

public SomeDependency(int clientID)
  • If they don't have that signature then the factory won't work; personally I don't like my DI to have to know anything about constructor params because it takes you out of purely dealing with interfaces and forcing you to implement specific ctor signatures on your concrete classes.

  • It also means that you can't reliably make your IDependentObjects be part of the whole DI process (i.e whereby they themselves have dependency graphs that you want the framework to resolve) because of the forced ctor signature.

  • For that reason I'd recommend that IDependentObject.DoSomething() itself be changed to DoSomething(int clientID) so that you can elide the new ConstructorArgument part of the factory code; this means that your IDependentObject s can now all have totally different ctor signatures, meaning they can have different dependencies if needs be. Of course this is just my opinion, and you will know what works best in your specific scenario.

Hope that helps.

Upvotes: 2

Related Questions