Introduction
While trying out the Microsoft Data Access application block for the NET 2.0 framework, I stumbled upon the Object Builder library. I liked the relative ease of a loosely coupled data access functionality of the Database Factory in the Data Access Block so I decided to see what made it tick. To my dismay, there was a miniscule amount of documentation on the subject of what the Object Builder used in it to create database objects for common operations. I did find a good web cast on MSDN that provided sample code for creating a container. Much of this article is based on this code. The Object Builder is the workhorse behind this. The Object Builder is a framework for implementing the Dependency Injection pattern. Think of Dependency Injection in abstract terms like this, using this pattern would allow you to bake a batch of cookies and determine what kind they were going to be when they were already done by injecting, chocolate chips, walnuts, etc. You can read more about the Dependency Injection pattern here.
This article is intended as the fast food version of using dependency injection with Microsoft's Object Builder which is part of the core assembly in Microsoft's Enterprise Library. The Object builder itself is not a class library that can be used by itself to create and inject objects into other objects. It is, however, a framework for building containers for dependency injection. A container is responsible for actually supplying the configuration for how the object will be built. You can specify strategies for creating an object. This demo uses a type mapping strategy; the object type is defined in an XML file (app.config).
Dependency Injection can be used when you have some type of implementation that may change in the future. This could be: business rules, a file format, or any other object that might change frequently and thus cause you headaches. Using this pattern you can create a separate assembly with a different implementation and inject it into your objects without having to recompile the code.
There are a multitude of instances where Dependency Injection would be useful. This demo illustrates using it in an environment where business rules change and the application must support theses changes. The Dependency Injection pattern helps us to extend existing objects without a need to recompile the whole application. This gives us a great deal of flexibility - one example is the Data Access Application Block as part of the Microsoft's Enterprise Library. Dependency Injection is used to allow for generic data access objects to be created, and configured through use of the application configuration file. The interface for the database is the same; for instance, Connect, Commit, Rollback, but the implementation can vary depending on the database Oracle, SQL Server, MySQL, etc .
Background
The Dependency Injection pattern is described in detail by Martin Fowler on his website.
Using the code
Required Resources
- Microsoft .NET Framework 3.0
- Microsoft Guidance Automation Framework
- Microsoft Enterprise Library (I used April 2007 Release, but Jan 2006 Release works as well)
- The Redmond Patterns and Practices Web Cast sample container code for Object Builder (I have included this code in my demo but you may download it here)
The scenario for the demo is a drive through for a fast food restaurant. The drive through repeats your order. When the restaurant started there was only one food item you could order, a hamburger with your choice of sauce. Now the restaurant wants to offer its "Fat Burger" with BBQ sauce without changing the code, but some locations don't offer the "Fat Burger" so this item must be configurable for these locations.
The code consists of the Object Builder Container from the Microsoft Patterns and Practices sample, a custom configuration handler that allows the container to be set up in the app.config file, an IFoodItem
interface, a hamburger
class, a fatburger
class, an order
class, a winform representing the drive through, and an XSD to validate the Container elements called BuilderXmlConfigElement.XSD
(Note: This is included in the source under a folder called Configuration. If you augment the code you must make sure that your BuilderXMLConfig
class points to this XSD in the ParseXmlConfiguration
Method).
The Container (From the patterns and practices web cast)
First let me try to explain the container without getting too drawn out. You can think of the container as a factory for an object that you would like to inject. It can control the lifetime of the object, and see whether it is a Singleton or not. It can also provide parameters for the constructor or other methods, just to name a few. There are different strategies you can use to set up the container. The demo uses a Type Mapping strategy which means a rule stating if I request an IFoodItem
to give me a hamburger
object. This rule is set up in the app.config in the demo. The container class DependencyContainer
is a class that allows the configuration of the object to be created to be set up by an XML string passed to the constructor. The container elements are constrained by a schema definition file that makes sure that the container contains the conforming elements that the Object Builder will understand and thus be able to utilize the custom container to create your object.
Because the container has a constructor that takes an XML string representing your type mapping strategy, all you have to do is pass a valid XML string that will conform to the XSD. I have created a custom configuration handler so I could include the configuration in the app.config that could then be used to create a valid XML string to send to the container (DependencyContainer
class) constructor.
The IConfigurationSectionHandler
only requires one override in order to implement. This override is the Create
method. Because I wanted this configuration section handler to basically give me an object with an XML string, I also overrode the ToString()
method of System.Object
so that calling it would return my XML string (Note: the Dependency Container Code actually does the validation of the XML string so it is not necessary in the configuration section handler code, but you could just as easily create a strongly typed configuration handler that validated the objects XML using the BuilderXMLConfigElement.XSD).
public object Create(object parent, object configContext,
System.Xml.XmlNode section)
{
XmlConfiguration config = new XmlConfiguration();
config._xml = "<containerconfig xmlns="container-config"> ";
config._xml = config._xml + section.InnerXml.Trim();
System.Xml.XmlNodeReader oReader = new System.Xml.XmlNodeReader(section);
config._xml = config._xml + oReader.ReadInnerXml();
config._xml = config._xml + "</containerconfig>";
System.Diagnostics.Debug.WriteLine(config._xml);
return config;
}
public override string ToString()
{
return _xml;
}
#endregion
Our Drive Through and Food Items
Our app.config file will contain the parameters for the dependency, and will be represented by a section called BurgerConfig
, which will be handled by the handler described above.
="1.0" ="utf-8"
<configuration>
<configSections>
<section name='BurgerConfig' type='ObjectBuilderDemo.XmlConfiguration,
ObjectBuilderDemo,Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null'/>
</configSections>
<BurgerConfig>
<Mappings>
<Mapping FromType='ObjectBuilderDemo.IFoodItem, ObjectBuilderDemo,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
ToType='ObjectBuilderDemo.Hamburger, ObjectBuilderDemo,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' />
</Mappings>
<BuildRules>
<BuildRule Type='ObjectBuilderDemo.Hamburger, ObjectBuilderDemo,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
Mode='Instance'>
<Constructor>
<Value Type='System.String'></Value>
</Constructor>
</BuildRule>
</BuildRules>
</BurgerConfig>
</configuration>
We have defined a mapping, and build rules in our app.config. The mapping states that we want our container to create a Hamburger
object when we request the IFoodItem
.
Our BuildRules
collection states a BuildRule
for our Hamburger
object which is assigned a Constructor BuildRule
that has a parameter of type String. This parameter provides the sauce parameter to the constructor of our burger objects. (Remember they get to choose the sauce.)
Our IFoodItem
interface contains a Method called GetIngredients()
which returns a list of strings containing the ingredients of the implementation.
public interface IFoodItem
{
System.Collections.Generic.IList<string> GetIngredients();
string GetSauce();
}
Our implementations are as follows. For the hamburger:
public IList GetIngredients()
{
burgerIngredients = new System.Collections.Generic.List<string>();
burgerIngredients.Add("Two all beef patties");
burgerIngredients.Add(GetSauce());
burgerIngredients.Add("Lettuce");
burgerIngredients.Add("Cheese");
burgerIngredients.Add("Pickles");
burgerIngredients.Add("Onions");
burgerIngredients.Add("Sesame Seed Bun");
return burgerIngredients;
}
With a method called GetSauce()
to get the sauce string passed to the constructor.
public Hamburger(string sauce)
{
if(sauce==null)
_sauce = "Special Sauce";
else
_sauce = sauce;
}
For our Fatburger we have:
public IList GetIngredients()
{
burgerIngredients = new System.Collections.Generic.List<string>();
burgerIngredients.Add("Three all beef patties");
burgerIngredients.Add("Slice of Ham");
burgerIngredients.Add(GetSauce());
burgerIngredients.Add("Lettuce");
burgerIngredients.Add("Swiss Cheese");
burgerIngredients.Add("American Cheese");
burgerIngredients.Add("Bacon");
burgerIngredients.Add("Pickles");
burgerIngredients.Add("Relish");
burgerIngredients.Add("Onion Rings");
burgerIngredients.Add("Mushrooms");
burgerIngredients.Add("Sesame Seed Bun");
return burgerIngredients;
}
We have a DriveThrough
class that contains a constructor that takes a IFoodItem
argument and contains a Order
property.
public DriveThrough(IFoodItem burger)
{
_burger = burger;
}
public string Order
{
get
{
{
string returnValue="";
foreach (string ingredient in _burger.GetIngredients())
{
returnValue = returnValue + ingredient +
System.Environment.NewLine;
}
return returnValue;
}
}
}
Finally we have the form with an event handler for the button click that Reads the configuration section handler for the BurgerConfig
, uses the container to return the appropriate burger by passing the DependencyContainer
the XML string from the app.config, creates a DriveThrough
object and passes our injected burger as a constructor argument, and finally displays a message box with the order.
private void btnGreet_Click(object sender, EventArgs e)
{
DriveThrough driveThrough;
XmlConfiguration o =
(XmlConfiguration)System.Configuration.ConfigurationManager.GetSection(
"BurgerConfig");
DependencyContainer container = new DependencyContainer(o.ToString());
ObjectBuilderDemo.IFoodItem food = container.Get();
driveThrough = new DriveThrough(food);
MessageBox.Show(driveThrough.Order);
}
If we run the application, our order is repeated using the ingredients from the hamburger. Let assume now we want to start selling our Fatburger, but we only want to offer it with BBQ Sauce. It's simple, all we have to do is re-define the mapping in our app.config and pass BBQ Sauce as our constructor parameter. See below:
="1.0" ="utf-8"
<configuration>
<configSections>
<section name='BurgerConfig' type='ObjectBuilderDemo.XmlConfiguration,
ObjectBuilderDemo,Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'/>
</configSections>
<BurgerConfig>
<Mappings>
<Mapping FromType='ObjectBuilderDemo.IFoodItem, ObjectBuilderDemo,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
ToType='ObjectBuilderDemo.Fatburger, ObjectBuilderDemo,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' />
</Mappings>
<BuildRules>
<BuildRule Type='ObjectBuilderDemo.Fatburger, ObjectBuilderDemo,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
Mode='Instance'>
<Constructor>
<Value Type='System.String'>BBQ Sauce</Value>
</Constructor>
</BuildRule>
</BuildRules>
</BurgerConfig>
</configuration>