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.
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)