Thursday, July 12, 2007 1:30 PM
This is the first post in what will (hopefully) be an occasional series on great tools that are found in many agile developers' toolbox. The goal of this series is to provide a light introduction to a given technology all the way from the ground up to being able to use the tool in some limited fashion...all in the period of about an hour. That said, this means that we'll likely be light on theory and edge cases of the tool allowing us to focus only on what we need to start using the tool and then showing you where to look when you need deeper answers.
Windsor is the
Inversion of Control (IoC) container piece of the
Castle Project, the same guys that bring you
MonoRail. In the short, IoC containers act as a holding structure for the components of your application allowing you to easily decouple them from one another. This gives you the flexibility to refactor as necessary, replace live implementations of objects with mocked ones, and basically do whatever is necessary to get your system running well in the shortest amount of time. Although we'll be talking about Windsor exclusively, you may want to check out other .Net IoC containers such as
Spring.NET or
Jeremy Miller's StructureMap.
Now that I know what it is, where do I get it? Head on over to the Castle Project's website and
download the installer. There's only one installer and it contains every Castle component available including
Windsor,
ActiveRecord, and
MonoRail. Once you have the framework installed, the necessary components will be added to your GAC and will even show up in the "Add References..." dialog of Visual Studio.
The only references that will be necessary to use the WindsorContainer are the Castle.Windsor and Castle.MicroKernel assemblies.
Briefly, let's talk about the example that we'll be using. Our example consumes a commerce web service, for the sake of our example, we'll assume its the
Amazon E-Commerce Service. This service is abstracted behind an ICommerceService interface which is implemented by a MockCommerceService, for testing, and an AmazonCommerceService for production. Although this implementation of the AmazonCommerceService does not actually communicate with Amazon.com we'll pretend for the sake of the example that it does.
Here's the interface as well as the two separate implementations of it...
/// <summary>
/// Provides a true implementation of the commerce service.
/// </summary>
public class CommerceService : ICommerceService
{
#region ICommerceService Members
/// <summary>
/// Gets the price of the given item.
/// </summary>
/// <param name="item">The item for which to retrieve the price.</param>
/// <returns>The price of the given item.</returns>
public string GetPrice(string item)
{
return "99.99";
}
#endregion
}
/// <summary>
/// Provides a mock implementation of the commerce service.
/// </summary>
public class MockCommerceService : ICommerceService
{
#region ICommerceService Members
/// <summary>
/// Gets the price of the given item.
/// </summary>
/// <param name="item">The item for which to retrieve the price.</param>
/// <returns>The price of the given item.</returns>
public string GetPrice(string item)
{
return "9.99";
}
#endregion
}
We've included two tests for this service, one categorized as "Live" and another categorized as "Mocked". We are able to simply switch between the two using the Windsor Container...
/// <summary>
///Fully exercises the <see cref="CommerceService"/> object.
/// </summary>
[TestFixture]
public class CommerceServiceTest
{
/// <summary>
/// Determines whether this instance can successfully interact with a mocked commerce service.
/// </summary>
[Test]
public void CanQueryCommerceService_Mocked()
{
WindsorContainer container = new WindsorContainer(new XmlInterpreter());
ICommerceService service = container.Resolve<ICommerceService>("mockCommerceService");
Assert.AreEqual("9.99", service.GetPrice("The Shins - Chutes Too Narrow"));
}
/// <summary>
/// Determines whether this instance can successfully interact with a live commerce service.
/// </summary>
[Test]
public void CanQueryCommerceService_Live()
{
WindsorContainer container = new WindsorContainer(new XmlInterpreter());
ICommerceService service = container.Resolve<ICommerceService>("liveCommerceService");
Assert.AreEqual("99.99", service.GetPrice("The Shins - Chutes Too Narrow"));
}
}
Notice that in both cases that we are using the Windsor Container as an abstract factory to create the specific implementation of ICommerceService that we desire. Let's step through the code...
The first thing we need to do is to create an instance of the WindsorContainer using an XmlInterpreter.
WindsorContainer container = new WindsorContainer(new XmlInterpreter());
This should give you a slight hint as to what's going on under the covers...we'll be using our app.config file in order to load the types that we need at runtime.
Next, we use the container to dynamically resolve our type depending on the implementation that we need...
ICommerceService service = container.Resolve<ICommerceService>("mockCommerceService");
There are two things to notice about the container.Resolve<>() method. First, this is a generic method so we simply have supply the type, or base type, that we intend to create as a type parameter. Second, this constructor takes an optional string specifying the ID of the specific implementation that we wish to create. Remember, this is an optional parameter, if we don't supply an ID then Windsor simply instantiates the first type of the given base type that it finds in the config file.
Speaking of the config file, as you've probably guessed from the presence of the XmlInterpreter class, the WindsorContainer finds out what it needs to know from the app.config file.
Let's take a look at it...
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="castle"
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
</configSections>
<castle>
<components>
<component id="mockCommerceService" service="ServiceLayer.ICommerceService, ServiceLayer"
type="ServiceLayer.MockCommerceService, ServiceLayer"/>
<component id="liveCommerceService" service="ServiceLayer.ICommerceService, ServiceLayer"
type="ServiceLayer.CommerceService, ServiceLayer"/>
</components>
</castle>
</configuration>
The config file has two sections that we're interested in. First, notice that that we're adding a section of type "castle" in the conifgSections node. This is simply how we tell .NET that we'll be handling a custom configuration section. We only need to supply the name of the node and the type that we'll be using to handle it.
Next, we have the castle section itself. This node simply has a 'components' node which contains an element for each type we're planning to instantiate. These nodes, called component nodes, have three key parts: an id, a service, and a type. The id is how we specify the component from code. Remember the string in the Resolve<>() method from the example above? This is the ID we were talking about. If we hadn't supplied an ID to the Resolve() method, Windsor simply would have instantiated the mockCommerceService component because it's the first component found in the file. Next up, we have the service attribute. The service attribute specifies the base type, or interface, that this particular component is implementing as well as the name of the assembly that it can be found in. Finally, we specify the type itself. The type is the actual concrete implementation of the component as well the name of the assembly that it is found in. Notice that in our example, both components specify the same value for service but different values for type. This is because we're actually offering two different implementations of the same interface...hence, the power of IoC containers.
That's it! You can download the code from
here, note that you'll need the Castle Project installed to be able to reference the assemblies from the GAC. This introduction is only the tip of the iceberg of even some of the simplest scenarios you can accomplish with the Windsor Container. For an excellent deeper dive into Windsor, check out Bitter Coder's series of tutorials
here, which have been conveniently collected into one place by
Insane World.