I'm trying to modify some existing code to use Castle Windsor as an IoC container.
The application in question is document-oriented. So it has an object graph that relies on data for specifying the data source that can't be known at registration time, and will change every time the object graph is resolved. However, this dependency is a few layers into the object graph, so I can't just pass it as an argument to container.Resolve()
since Windsor doesn't propagate inline dependencies.
The solution I've come up with instead is to use the typed factory facility to generate a factory which can resolve every dependency in the graph, and have Document
's constructor take an instance of this factory as well as a string specifying the data source. The factory is specified at registration time, and the data source is specified at resolution time. The constructor takes over from there, manually polling the factory to resolve is dependencies. The result looks something like this:
public interface IDataSource { /* . . . */ }
public interface IWidgetRepository { /* . . . */ }
public interface IWhatsitRepository { /* . . . */ }
// Implementation generated by Windsor's typed factory facility
public interface IFactory
{
IDataSource CreateDataSource(string path);
IWidgetRepository CreateWidgetRepository(IDataSource dataSource);
IWhatsitRepository CreateWhatsitRepository(IDataSource dataSource);
void Release(IDataSource dataSource);
void Release(IWidgetRepository widgetRepository);
void Release(IWhatsitRepository whatsitRepository);
}
public class Document
{
private readonly IDataSource _dataSource;
private readonly IWidgetRepository _widgetRepository;
private readonly IWhatsitRepository _whatsitRepository;
public Document (IFactory factory, string dataSourcePath)
{
_dataSource = factory.CreateDataSource(dataSourcePath);
_widgetRepository = factory.CreateWidgetRepository(_dataSource);
_whatsitRepository = factory.CreateWhatsitRepository(_dataSource);
}
}
This absolutely works, and does accomplish the goal of having Windsor take charge of dependency resolution. Or at least, I can easily reconfigure the application by modifying the registration code. At the moment I'm still making one call to container.Resolve()
for every Document
that gets created, but I believe that sin can easily be amended with a second Typed Factory.
However, it still feels wrong. Windsor is in charge of newing up objects and (somewhat) managing their lifetimes, sure. But it's not really injecting those dependencies into Document
's constructor; instead the constructor is pulling them out of the factory. Worse, by passing the instance of IDataSource
into the factory methods, it's taken charge of managing the object graph. That seems like a huge subversion of the inversion to me.
So, what am I missing?
EDIT
To clarify, what I think I'm supposed to be shooting for is for Document
's constructor to look more like the below.
public Document (IDataSource dataSource, IWidgetRepository widgetRepository, IWhatsitRepository whatsitRepository)
{
_dataSource = dataSource;
_widgetRepository = widgetRepository;
_whatsitRepository = whatsitRepository;
}
That way Windsor is in direct control of supplying all the objects' dependencies using constructor injection. That's actually what the constructor signature looks like in the original code, but I was unable to get it to work with Windsor because container.Resolve()
doesn't propagate inline dependency parameters. So I can't just do:
var document = container.Resolve<IDocument>(new { dataSourcePath = somePath }); // throws an exception
because Windsor doesn't pass dataSourcePath
on to DataSource
's constructor, much less make sure that a DataSource
instantiated with that path is passed on to the other constructors.
An answer to another question points out that this is by design - doing otherwise would introduce coupling. This is a no-no, since one shouldn't mandate or assume that implementations of an interface have particular dependencies. Sadly, that points out another way I think the code I came up with is wrong, since Factory.CreateWidgetRepository()
and Factory.CreateWhatsitRepository()
imply just that sort of assumption.