This post is part 3 in a series devoted to extending CruiseControl.NET to support multiple build configurations for a single project. In this post, I will cover a single topic: ccnet’s method of dependency injection.
Constructor Dependency Injection
The ccnet docs for developing dashboard plugins contains a section titled “Defining dependencies”, which states:
The CruiseControl.NET Web Dashboard uses a Constructor Dependency Injection (CDI) pattern to enable classes to define what types they are dependent on. The Dashboard API has a number of types you can depend upon which can do things like return you the currently viewed build log. You can also specify dependencies to your own types. It is recommended that you use interfaces to define responsibilities.
The Dashboard's CDI implementation does not currently allow for runtime configuration. It will use sensible defaults where available. We plan on adding more configuration later.
That’s all great, but what does it mean? From my first pass of the docs, I understood that somehow my constructor would be analyzed and the correct object references would be provided to my plugin. A fine theory, but how does it work in practice? Specifically, *which* objects would I receive?
Again I consulted the ccnet’s source code. I referred to the handy ProjectReportProjectPlugin class, and found the following constructor:
public ProjectReportProjectPlugin(IFarmService farmService, IVelocityViewGenerator viewGenerator, ILinkFactory linkFactory)
{
}
This class is receiving a reference to the IFarmService interface, IVelociyViewGenerator interface, and the ILinkFactory interface. I copied these same arguments into the constructor for my own plugin, and attached to the asp.net process. I refreshed the dashboard page, caught the break point in the debugger, and sure enough, my constructor received a valid object reference for each argument. It must be magic.
Constructor Argument Type Availability
I’ve now established that at the minimum I can receive valid object references for three interfaces. But what else is available? At this point, I didn’t know what any of these interfaces actually *did*, so I was worried about obtaining dashboard services that might or might not be available through these interfaces.
Still attached to the debugger, I used the Call Stack window to go backwards and find out what was actually instantiating my plugin. This technique is pivotal; you will use this technique many times during the development of your plugin, depending on the services your plugin requires.
Following the call stack backwards shows a bunch of methods being executed within the NetReflector assembly, and the first method one can find that is debuggable is ThoughtWorks.CruiseControl.WebDashboard.DashboardConfigurationLoader.Load method. The last line of the method looks like this:
return NetReflector.Read(node, typeTable);
Evidently, the dashboard is relying upon the NetReflector runtime to deserialize all of the plugins from the dashboard config file. The read method accepts an xml node, and a NetReflectorTypeTable. This guy proves very interesting.
The NetReflectorTypeTable has a member named “instantiator”, of type ObjectionNetReflectorInstantiator. That object has a member named “objectStore”, of type ObjectionStore. All of these objects and members are viewable from the runtime type inspector.
The ObjectionStore apparently contains all of the reflection information and available object instances. The ObjectionStore has a hashtable named “implementationTypes”, and it is this hashtable that I used to determine which objects were available to be injected into my constructor.
My implementationTypes map has 17 types available. I have no idea if these types are universal; I imagine the contents of the implementationTypes table will vary depending on the deployment. Inspection of my copy of this table yields the following types:
ThoughtWorks.CruiseControl.WebDashboard.IO.ICruiseRequest
ThoughtWorks.CruiseControl.Core.Reporting.Dashboard.Navigation.IPhysicalApplicationPathProvider
ThoughtWorks.CruiseControl.Remote.ICruiseManagerFactory
ThoughtWorks.CruiseControl.WebDashboard.Dashboard.ILinkFactory
ThoughtWorks.CruiseControl.Core.Reporting.Dashboard.Navigation.ICruiseUrlBuilder
ThoughtWorks.CruiseControl.WebDashboard.Dashboard.IAllBuildsViewBuilder
ThoughtWorks.CruiseControl.WebDashboard.MVC.View.IVelocityViewTransformer
ThoughtWorks.CruiseControl.WebDashboard.ServerConnection.ICruiseManagerWrapper
ThoughtWorks.CruiseControl.WebDashboard.Dashboard.IProjectGrid
ThoughtWorks.CruiseControl.WebDashboard.ServerConnection.IFarmService
ThoughtWorks.CruiseControl.WebDashboard.Dashboard.ILinkListFactory
ThoughtWorks.CruiseControl.WebDashboard.Dashboard.IProjectGridAction
ThoughtWorks.CruiseControl.WebDashboard.Dashboard.IActionInstantiator
ThoughtWorks.CruiseControl.WebDashboard.Configuration.IDashboardConfiguration
ThoughtWorks.CruiseControl.Core.Util.IMultiTransformer
ThoughtWorks.CruiseControl.WebDashboard.MVC.View.IVelocityViewGenerator
ThoughtWorks.CruiseControl.WebDashboard.Dashboard.IBuildNameFormatter
I really don’t know what most of these objects do, but it’s not too hard to figure out when you have the source code.
Now you know how to determine what types are available for your constructor at runtime. From here, you can consult the source code to determine what services these interfaces provide, and parameterize your constructor accordingly.
The next post will cover the use of the Velocity subsystem.