Search code examples
c#dependency-injectioncastle-windsor

Castle Windsor Abstract Factory


I'm trying to understand how to use TypedFactoryFacility to create an abstract factory, and I have it working at a basic level, however I don't fully understand how to scale it with runtime dependencies

Suppose I have a service that needs to be created at runtime:

public interface IRuntimeService {
  void DoThing();
}

with the following implementation

public class RuntimeService : IRuntimeService {
  public void DoThing() {
    // Do some work
  }
}

To create my IRuntimeService, I've created an abstract factory

public interface IRuntimeServiceFactory {
  IRuntimeService CreateService();
}

In my Castle installer, I'm using the TypedFactoryFacility to register my class and abstract factory.

public class TypeInstaller : IWindsorInstaller {

  public void Install(IWindsorContainer container, IConfigurationStore store) {
    container.AddFacility<TypedFactoryFacility>();
    container.Register(Component.For<IRuntimeService>().ImplementedBy<RuntimeService>());
    container.Register(Component.For<IRuntimeServiceFactory>().AsFactory());
  }

Then in my class that will be using the service, I can use the factory to create new service instances at runtime.

var myService = m_ServiceFactory.CreateService();

Everything above works perfectly, however I'm running into a problem when my RuntimeService class needs to be injected with a dependency chain itself that include runtime parameters.

To expand the example above, suppose I have a new runtime dependency

public interface IRuntimeDependency {
  void DoWork();
}

implemented by a class that takes a runtime string value through the constructor

public class RuntimeDependency : IRuntimeDependency {

  private readonly string m_Param;

  public RuntimeDependency(string param) {
    m_Param = param;
  }

  public void DoWork() {
    // Do work involving the param
  }
}

And the previously defined service class now needs a reference to the dependency

public class RuntimeService : IRuntimeService {

  private readonly IRuntimeDependency m_Dep;

  public RuntimeService(IRuntimeDependency dep) {
    m_Dep = dep;
  }

  public void DoThing() {
    // Do some work involving the dependency
    m_Dep.DoWork();
  }
}

How do I now I create instances of my service using the TypedFactoryFacility?

I would expect do just be able to change my factory method to look like

IRuntimeService CreateService(string param);

but Windsor throws an error 'Could not resolve non-optional dependency for parameter 'param' type 'System.String'.

Windsor knows how to create an IRuntimeDependency if I give it a string, and it knows how to create a IRuntimeService if I give it the dependency, so why can't it directly create a IRuntimeService with the string param?

I can make it work by having two distinct factory methods

IRuntimeService CreateService(IRuntimeDependency dep);
IRuntimeDependency CreateDependency(string param);

and creating the dependency, manually myself

var dep = m_ServiceFactory.CreateDependency(param);
var myService = m_ServiceFactory.CreateService(dep );

^^^This works, but the whole point of using a container is so that it will take care of assembling new objects for me. This is a relatively simple example involving only one dependency, but it would easily grow out of control with a more complex object graph.

I could of course create my own factory implementations, but that also nullifies the benefit of using the TypedFactoryFacility which is supposed to create the abstract factory implementations for you. I have a hard time believing there's not an existing solution to this problem but the Windsor examples don't contain any chained run-time dependencies.

I don't think using a FactoryComponentSelector is the correct approach because there's only one possible path to create the RuntimeService instance. It should be able to auto-resolve.


Solution

  • It is possible using DynamicParameters.

    container.Register(Component.For<IRuntimeService>()
        .ImplementedBy<RuntimeService>()
        .LifestyleTransient()
        .DynamicParameters((k, d) => {
            d["dep"] = new RuntimeDependency((string)d["param"]);
        }));
    

    Keep in mind that the dictionary keys have to match the parameter names in the CreateService method and RuntimeService constructor.

    Edit: You should also make it LifestyleTransient if you intend to create a new instance each time the factory method is called. (The default is singleton)