Dependency Injection (or IoC) is a practice I apply when designing software. It enables enforcement of the Single Responsibility Principle and also has the added side effect of producing more testable code.
Scott Bellware has posted an excellent write-up on dependency patterns and it got me thinking about my favoured approach with dependency injection and how the Castle Windsor container provides that for us. I favour constructor based DI as it enforces the fact that your class must be wired up during initialisation. It makes your dependencies highly visible and as Scott mentions – goes some way to producing a self-documenting API.
Some of the common alternatives, or perhaps complimentary approaches include resolving dependencies from the container or service locator hidden in the constructor (dirty), or the provision of property setters for your dependent interfaces. Of course providing getters (or at least public getters) is a no-no as you would be exposing your dependencies to consumers thus violating encapsulation. But I guess this goes without saying?
Thankfully, Monorail controllers and their dependencies can be wired up using constructor based DI via RailsFacility and Windsor integration. Take the following example:
ContactController in the diagram above is a simple Monorail controller that depends on the IContactService to carry out most of its work. Stemming from there the IContactService implementation has dependencies on IContactRepository and an external library (from NSpectre) interface IValidatorFactory which is created using the Windsor factory facility and some custom factory code, but more on that later… As you can see from the model, the dependencies are injected via constructor arguments.
Using the RailsFacility is fairly simple, rather than me explaining it I’ll point you to the docs here. Once you’ve got the facility and container set up, you simple declare your controllers and their dependent components via configuration:
<component id="contact.controller" type="Campaigns.Controllers.ContactController, Campaigns.Controllers">
<parameters>
<mailTo>benjamin.lovell@gmail.com</mailTo>
<subject>Contact message from Website</subject>
</parameters>
</component>
I’ve explicitly declared some of the required values for the ContactController constructor above. Just as reminder the signature for ContactController constructor:
public ContactController(IContactService contactService, string mailTo, string subject)
When the ContactController is wired up by windsor the IContactService contactService argument is resolved to the following service configured in my components.config file:
<component id="contact-service"
service="Campaigns.Core.IContactService, Campaigns.Core"
type="Campaigns.Services.ContactService, Campaigns.Services">
</component>
Of course, ContactService also joins in the fun and has its dependencies injected via the same means:
<component id="contact-repository"
service="Campaigns.Core.IContactRepository, Campaigns.Core"
type="Campaigns.Repository.ContactRepository, Campaigns.Repository" />
Windsor detects that the ContactService constructor takes a IContactRepository argument and resolves this automatically.
Now for the keen-eyed among you, and going back to my point earlier regarding the IValidatorFactory dependency… This is resolved using the natty Windsor factory facility, the best explanation of which is here. In my configuration I state that IValidatorFactory dependencies should be resolved via my custom factory code:
using NSpectre.Core;
using NSpectre.Core.Configuration;
using NSpectre.Core.Implementation;
namespace Campaigns.Core
{
/// <summary>
/// Factory for NSpectre validators
/// </summary>
public class NSpectreFactory : INSpectreFactory
{
#region Fields
private readonly string xmlEmbeddedResourcePath;
private readonly bool saveGeneratedCode = false;
private readonly string path;
#endregion
#region Constructors
/// <summary>
/// Initialises the factory with the embedded resource path.
/// </summary>
/// <param name="xmlEmbeddedResourcePath">The path to the embedded resource.</param>
/// <param name="saveGeneratedCode">Save the generated code</param>
/// <param name="path">The path to save the code to</param>
public NSpectreFactory(string xmlEmbeddedResourcePath, bool saveGeneratedCode, string path) : this(xmlEmbeddedResourcePath)
{
this.saveGeneratedCode = saveGeneratedCode;
this.path = path;
}
/// <summary>
/// Initialises the factory with the path to the NSpectre configuration embedded resource.
/// </summary>
/// <param name="xmlEmbeddedResourcePath"></param>
public NSpectreFactory(string xmlEmbeddedResourcePath)
{
this.xmlEmbeddedResourcePath = xmlEmbeddedResourcePath;
}
#endregion
#region Properties
/// <summary>
/// Gets the path to the configuration embedded resource.
/// </summary>
public string XmlEmbeddedResourcePath
{
get { return xmlEmbeddedResourcePath; }
}
/// <summary>
/// Gets a flag indicating whether NSpectre should save the generated code.
/// </summary>
public bool SaveGeneratedCode
{
get { return saveGeneratedCode; }
}
/// <summary>
/// Gets the path to save the generated code to.
/// </summary>
public string Path
{
get { return path; }
}
#endregion
#region Methods
/// <summary>
/// Creates the validator factory
/// </summary>
/// <returns>The validator factory, initialised</returns>
public IValidatorFactory CreateFactory()
{
IConfigurationReader reader = new EmbbeddedXmlResourceConfigurationReader(xmlEmbeddedResourcePath, new NullLogger());
Initialiser initialiser = new Initialiser();
if (SaveGeneratedCode)
return initialiser.CreateValidatorFactory(reader, saveGeneratedCode, path);
else
return initialiser.CreateValidatorFactory(reader);
}
#endregion
}
}
The custom factory is hooked up using the following configuration:
<component id="nspectre.factory"
service="Campaigns.Core.INSpectreFactory, Campaigns.Core"
type="Campaigns.Core.NSpectreFactory, Campaigns.Core">
<parameters>
<xmlEmbeddedResourcePath>Campaigns.Core.Model.NSpectreValidations.xml, Campaigns.Core</xmlEmbeddedResourcePath>
</parameters>
</component>
<component id="nspectre.default"
type="NSpectre.Core.IValidatorFactory, NSpectre.Core"
factoryId="nspectre.factory"
factoryCreate="CreateFactory" />
You really notice the effectiveness of this approach when adding new controllers to your project. You simply add the controller code, define its dependencies in the constructor and add the configuration for the controller to your controllers.config file and everything is resolved and injected for you at runtime. Very nice indeed, I’m sure you will agree!
Testing is made easy by providing dynamic mocks. To make this an even nicer experience take a look at the AutoMockingContainer from the nice folks at Eleutian!
To wrap up I have to say: Castle really does kick the llama’s ass.