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.
I believe I've found the correct solution. Apparently the available documentation wasn't quite explicit (verbose?) enough to bang the concept through my thick skull the first twelve times I read it, so I'll make an attempt to document it in more detail here for the sake of others who might be as helpless as I am. (I'll also let it sit a while before accepting in the hope that someone else might come along with any other/better suggestions.)
Long story short, Typed Factory Facility is the wrong tool for the job.
--
The trick is to use the DynamicParameters facility in the fluent registration API, which is (rather sparsely) documented here and in the last section here.
What DynamicParameters lets you do is directly modify the collection of inline parameters that was supplied when the container was asked to resolve a component. This dictionary can then get passed on up the resolution pipeline, making it available to sub-dependencies.
DynamicParametershas three overloads, each taking a single delegate as its parameter. These delegate types aren't explicitly documented, so for the sake of posterity here's what ReSharper (I'm too lazy to download the source.) tells me their declarations look like:DynamicParametersDelegateis for the most basic case, where you just need to supply parameters that won't be managed by the container. That probably works for me, but in keeping with my tendency to find the complicated option first I ended up making a beeline for the second option, which is to supply a delegate which manually pulls dynamic parameters out of the container. Since Windsor's managing the component's lifetime in that case, you need to make sure it's being released. That's whereDynamicParametersResolveDelegatecomes in - it's just like the first one, except it also returns aComponentReleasingDelegate(public delegate void ComponentReleasingDelegate(IKernel kernel);) which Windsor can use to release components at the appropriate time.(The third,
DynamicParametersWithContextResolveDelegate, is presumably for modifying the creation context. I don't know enough about how Windsor works to actually understand what the preceding sentence means, so I'll have to leave it at that.)That allows me replace the constructor from my example with the much better-looking:
The factory is removed entirely. Instead, the magic goes in the component registration code for
IDocument:So now I can ask for an
IDocumentwith exactly the line of code I had been looking for:and the container starts by invoking that
DynamicParametersdelegate, which supplies the container with aDataSourcefor the supplied path. From there the container's able to figure the rest out for itself so that the same instance ofDataSourcegets passed to the constructors for all three of the other objects in the dependency graph.