Introduction
In this installment of the series, I'll show you how you can use LinFu
's Simple.IOC
container to quickly add Dependency Injection capabilities to your application. I'll also discuss some of the common features found in most inversion-of-control containers and I will show you how Simple.IOC
can perform those same features without forcing you to decipher its object model (or lack thereof).
Note: If you're wondering what the entire LinFu
framework is about, click here.
Background
When I discovered how to write LinFu.DesignByContract1
about three years ago, I quickly realized that I needed to find a way to wrap every object that I created into a contract that LinFu.DesignByContract1
could enforce. The problem was that the only way to tie an object to a contract was to wrap it with an interceptor using LinFu.DynamicProxy
upon object instantiation.
The thought of having to clutter my code with direct references to ProxyFactory
and LinFu.DesignByContract
didn't sound too appealing. I needed a better way to abstract the details of object construction away from the client code. The client should have no knowledge about the existence of LinFu.DesignByContract
and all object instantiations should be centralized into a single source.
For me, using an inversion-of-control container was the best way to meet these requirements. I therefore set out to find a library that I could easily use without having to spend more than five minutes reading its documentation or being forced to spend even more time trying to figure out its object model. More importantly, I needed a container so simple that it required little to no configuration. The container should be able to assemble itself without requiring me to write code to wire all the dependencies together. It also shouldn't force me to spend time trying to learn someone else's XML syntax.
What About…?
I looked at various IOC containers such as Castle's Windsor container, Spring.NET and others, but they didn't meet my needs for simplicity. They all had an impressive number of features, but I knew that I wasn't going to use all of their respective feature sets. I also knew that I didn't have the time to write that many configuration files to get my application up and running. Since none of those containers fit my needs, I decided to write my own. That's how Simple.IOC
came into existence.
Features and Usage
In the following sections, I'll briefly discuss the features commonly found in an IOC container and I'll show you how easy it is to implement the same features using LinFu
. Before I talk about how to use Simple.IOC
's features, however, we're going to need a model to use with the container. Here it is:
An Example Model
Note the following example model, in pseudo-code:
A vehicle has an engine.
A vehicle also has a driver, which is a person.
Vehicles can either move or park.
An engine can either start or stop.
A person has a name and an age.
Using this description, one can glean an object model that results in the following code:
public interface IVehicle
{
void Move();
void Park();
IPerson Driver { get; set; }
IEngine Engine { get; set; }
}
public interface IEngine
{
void Start();
void Stop();
}
public interface IPerson
{
string Name { get; set; }
int Age { get; set; }
}
Interfaces?
The first thing that you might notice here is that I'm actually using interfaces for the model rather than using either an abstract base class or a concrete class. Using interfaces allows you to easily use other parts of the LinFu
framework (in libraries such as DynamicProxy
or DynamicObject
) and it gives the Simple.IOC
container the most amount of flexibility when dealing with your type instances. In any case, this model should be sufficient for this discussion, so let's move on to the next section.
Object Instantiation
One of the most important features that any IOC container can provide is the ability to separate the details of object construction from its actual usage. For the Simple.IOC
container, accessing an IVehicle
instance is as easy as:
SimpleContainer container = new SimpleContainer();
IVehicle vehicle = container.GetService<IVehicle>();
Vehicle.Move();
In this example, the client code knows nothing about the concrete class that implements the IVehicle
interface. The client assumes that the container is capable of providing an IVehicle
implementation, but at this point we haven't injected any dependencies into the container yet. If that's the case, then how do you inject services into the container?
Dependency Injection
In order to ensure that there is a working implementation of the IVehicle
interface, we need to make sure that the container can create instances of IVehicle
by providing a concrete class implementation that the container can use. Assuming that I have the following Car
class defined in a DLL named CarLibrary.dll:
[Implements(typeof(IVehicle), LifecycleType.OncePerRequest)]
public class Car : IVehicle
{
private IEngine _engine;
private IPerson _person;
public Car()
{
}
public IEngine Engine
{
get { return _engine; }
set { _engine = value; }
}
public IPerson Driver
{
get { return _person; }
set { _person = value; }
}
public void Move()
{
if (_engine == null || _person == null)
return;
_engine.Start();
Console.WriteLine("{0} says: I'm moving!", _person.Name);
}
public void Park()
{
if (_engine == null || _person == null)
return;
_engine.Stop();
Console.WriteLine("{0} says: I'm parked!" , _person.Name);
}
}
Most inversion-of-control containers would use an external XML file to describe what should be injected into the container. That's not the case with LinFu
. LinFu
, in contrast, only requires a single attribute declaration declared on each concrete class to determine which dependencies should be injected into the container. Since we are only loading CarLibrary.dll, loading that assembly is as easy as:
string directory = AppDomain.CurrentDomain.BaseDirectory;
IContainer container = new SimpleContainer();
Loader loader = new Loader(container);
loader.LoadDirectory(directory, "CarLibrary.dll");
The loader will scan the CarLibrary.dll assembly for any types that have ImplementsAttribute
defined and it will inject each of those types into the container. All you have to do is define ImplementsAttribute
in your class and the loader will handle the rest. The first attribute parameter describes the interface type that your class will be implementing, while the second parameter denotes the lifecycle of the type that you wish to inject. While the first parameter is self-explanatory, the lifecycle type needs a bit more explanation.
Lifecycle Management
Like other IOC containers, LinFu
supports the following lifecycle types:
Once per Request
Every time a client requests a service instance from the container, the container will create a brand new instance for your client to use. No two instances that are instantiated will ever be the same reference. In the previous example, we specified LifecycleType.OncePerRequest
on the Car
class to indicate that every vehicle created from that container will be a new and separate vehicle.
Once per Thread
In contrast to LifecycleType.OncePerRequest
, LifecycleType.OncePerThread
tells the loader to create one instance of your target type for every thread that requests an instance of an IVehicle
type. This is effectively a thread-wide singleton. The corresponding declaration would be:
[Implements(typeof(IVehicle), LifecycleType.OncePerThread)]
...
If you need an application-wide singleton, on the other hand, then the next option might be what you're looking for.
Singleton Instancing
With the LifecycleType.Singleton
option, no matter how many times your client code might request an instance of a particular interface, once that instance has been created by the container, you will always get the same interface instance every time the GetService
()
method is called. This might be useful if you have a shared component that needs to be accessed from anywhere in your application. The equivalent declaration would be:
[Implements(typeof(IVehicle), LifecycleType.Singleton)]
...
Property Setter Injection
The next thing that you might have noticed is that IVehicle
was defined with two respective properties named Driver
and Engine
, yet none of the examples show you how to make Simple.IOC
inject those dependencies into the type automatically. This is because, by default, Simple.IOC
lets concrete types inject their own dependencies from the container by having them implement the IInitialize
interface:
public interface IInitialize
{
void Initialize(IContainer container);
}
When an object is created by the container, it checks if the newly-created instance implements IInitialize
. If that object implements IInitialize
, the container effectively "introduces" itself to that object through the container
parameter, allowing that object to initialize itself with any services that the container might already have:
[Implements(typeof(IVehicle), LifecycleType.OncePerRequest)]
public class Car : IVehicle, IInitialize
{
public void Initialize(IContainer container)
{
_engine = container.GetService<IEngine>();
_person = container.GetService<IPerson>();
}
}
Since each type is responsible for injecting its own property dependencies from the container, the container doesn't have to know anything about injecting those property dependencies into each type. This makes the code for Simple.IOC
incredibly easy to maintain. It also allows you to ensure that a container has all the dependencies your components require upon initialization.
Rolling Your Own Property Injector
If, for some reason, Simple.IOC
's default property injection behavior doesn't meet your needs, you can always create your own property injector using the IPropertyInjector
interface:
public interface IPropertyInjector
{
bool CanInject(object instance, IContainer sourceContainer);
void InjectProperties(object instance, IContainer sourceContainer);
}
For example, Simple.IOC
uses the following implementation of IPropertyInjector
by default:
public class DefaultPropertyInjector : IPropertyInjector
{
public bool CanInject(object instance, IContainer sourceContainer)
{
return instance is IInitialize;
}
public void InjectProperties(object instance, IContainer sourceContainer)
{
if (!(instance is IInitialize))
return;
IInitialize init = instance as IInitialize;
init.Initialize(sourceContainer);
}
}
As you can see here, there's nothing special about DefaultPropertyInjector
, aside from its simplicity. Once you have written your own custom implementation of IPropertyInjector
, all you need to do is add it to the container's PropertyInjectors
collection:
MyCustomInjector injector = new MyCustomInjector();
container.PropertyInjectors.add(injector);
Every time the container creates a new object, it asks every IPropertyInjector
instance in that collection if it can inject dependencies into that type by using the CanInject()
method. Once a property injector tells the container that it can inject properties into the target type, the container calls the InjectProperties()
method, leaving the injector to perform the property injection on its own.
Constructor Injection
Unlike other containers, Simple.IOC
doesn't support the traditional form of constructor injection. This is because it creates an unnecessary coupling between object instantiation and object initialization. The container should know nothing about the types it creates and it should know nothing about the constructors which that type might contain. The next question you might be asking is, "If the container can only create objects with default constructors, how do I create types that don't have a default constructor?"
Creating Your Own Factory
As it turns out, Simple.IOC
makes it easy to create objects that don't have a default constructor. Let's suppose, for example, that the Car
class was modified to only have the following constructor:
public Car(IEngine engine, IPerson driver);
...and let's further suppose that every time there was a request for an IVehicle
instance, we wanted to use whatever IEngine
and IPerson
implementations the container had at the time the object was created.
A Layer of Indirection
In order to create your own factory, all you have to do is implement the IFactory
(
Of T)
interface and define FactoryAttribute
on your factory class:
[Factory(typeof(IVehicle))]
public class CarFactory : IFactory<IVehicle>
{
public IVehicle CreateInstance(IContainer container)
{
IEngine engine = container.GetService<IEngine>();
IPerson driver = container.GetService<IPerson>();
Car newCar = new Car(engine, driver);
return newCar;
}
}
The only thing left to do at this point is load your custom factory into the container. Fortunately, there's no additional code that you have to write in order to make this happen. Once you call the initial Loader.LoadDirectory
(
)
method, all dependencies (including custom factories) are loaded into the container automatically. All you have to do is make sure that your custom CarFactory
assembly is placed in the same directory as the one specified in the Loader.LoadDirectory
()
call. The loader will take care of the rest of the details for you.
Method Call Interception
Like other containers, Simple.IOC
has features that allow you to intercept method calls made to instances that the container creates. In order to intercept instances given by the container, all you have to do is implement the ITypeInjector
interface:
public interface ITypeInjector
{
bool CanInject(Type serviceType, object instance);
object Inject(Type serviceType, object instance);
}
The CanInject
(
)
method will tell the container whether or not you can provide an interceptor (presumably a proxy) for that given service type. The Inject()
method will replace the service instance with an interceptor itself. Once you have written your own custom implementation of ITypeInjector
, all you have to do is add it to the TypeInjectors
collection that is defined on the container instance:
ITypeInjector yourInjector = new SomeTypeInjector();
Container.TypeInjectors.Add(yourInjector);
With this approach, transparently adding additional behavior to your interface instances becomes practically trivial when combined with LinFu.DynamicProxy
. When used with LinFu.DynamicProxy
, adding another set of features to an existing interface implementation only requires you to add another interceptor. I'll leave it to the reader to decide what to do with this feature. The possibilities are endless.
Extensibility
Nearly every IOC container library has at least one or two features that allow it to extend its feature set using plug-ins. In LinFu
's case, the Simple.IOC
container supports plug-ins through the use of the IContainerPlugin
interface:
public interface IContainerPlugin
{
void BeginLoad(IContainer container);
void EndLoad(IContainer container);
}
The BeginLoad
and EndLoad
methods will be called before and after the loader loads the list of types into the container. To implement your own plug-in, all you would have to do is write something similar to the following:
[ContainerPlugin]
public class SamplePlugin : IContainerPlugin
{
void BeginLoad(IContainer container)
{
Console.WriteLine("Load started");
}
void EndLoad(IContainer container)
{
Console.WriteLine("Load completed");
}
}
…and just as it's done with the custom CarFactory
implementation, you don't need to add any additional code to load this into the container. The loader will detect the ContainerPluginAttribute
declared on SamplePlugin
and it will load it into the container once Loader.LoadDirectory
(
)
is called.
Points of Interest
Inversion of Control, Implement Thyself
Probably one of the most interesting things that you might notice about the Simple.IOC
container is that the container itself was actually built using inversion-of-control principles. It relies heavily on interfaces to do most of its work and it truly knows nothing about the classes that implement those interfaces.
Choosing Configuration through Reflection Instead of XML
While XML is (by definition) extensible and self-describing, it seemed like some developers were reinventing the wheel by trying to describe dependencies that were already contained in every .NET assembly ever compiled. For me, it made more sense to query the existing metadata embedded in an assembly rather than parse an external XML file that had more or less the same information. Thus, I chose to skip implementing an XML-based configuration.
Coming Up in the Next Article
In Part V of this series, I'll show you how you can use LinFu.DesignByContract2
to add language-independent Design by Contract features to your favorite .NET language without having to switch to Eiffel or Eiffel.NET. LinFu.DesignByContract2
allows you to define type contracts in the following fashion:
[ContractFor(typeof(IDbConnection))]
public interface IConnectionContract
{
[RequireConnectionStringNotEmpty] void Open();
}
RequireConnectionStringNotEmptyAttribute
is actually a precondition that is defined as:
[AttributeUsage(AttributeTargets.Method)]
public class RequireConnectionStringNotEmptyAttribute : Attribute,
IPrecondition
{
public bool Check(object target, InvocationInfo info)
{
IDbConnection connection = target as IDbConnection;
if (connection == null)
return true;
return !string.IsNullOrEmpty(connection.ConnectionString);
}
public void ShowError(TextWriter output, object traget, InvocationInfo)
{
Output.WriteLine("The connection string cannot be null or empty!");
}
public bool AppliesTo(object target, InvocationInfo info)
{
IDbConnection connection = target as IDbConnection;
if (connection == null)
return false;
return true;
}
public void Catch(Exception ex)
{
}
}
To make this even more interesting, we're going to be using the contract in VB.NET:
Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports LinFu.DesignByContract2.Injectors
Imports Simple.IoC
Imports Simple.IoC.Loaders
Module Module1
Sub Main()
Dim container As New SimpleContainer()
Dim loader As New Loader(container)
Dim directory As String = AppDomain.CurrentDomain.BaseDirectory
loader.LoadDirectory(directory, _
"LinFu.DesignByContract2.Injectors.dll")
loader.LoadDirectory(directory, "*.dll")
Dim contractLoader As IContractLoader = _
container.GetService(Of IContractLoader)()
contractLoader.LoadDirectory(directory, "SampleContracts.dll")
container.AddService(Of IDbConnection)(New SqlConnection())
Dim connection As IDbConnection = _
container.GetService(Of IDbConnection)()
connection.Open()
End Sub
End Module
Believe it or not, that last call to connection.Open
(
)
will never be executed. Instead, the contract verifier will throw the following exception:
The error message displayed in the exception should look familiar; it's the same one that we defined in the example above. Think about that for one second. LinFu.DesignByContract allows you to pass contracts around as simple DLL files. Unlike Eiffel, the contracts can actually be swapped between languages and passed around as reusable libraries. The onus is on you to decide what to do with this feature. Suffice it to say that after using LinFu.DbC2
, you might never develop the same way again.
Nearly Transparent
Aside from the container setup code in the VB.NET example above, the client is completely oblivious to the existence of a contract. Adding new contracts is as easy as copying new contract libraries to the application directory. Once those contracts have been placed into that directory, the only thing left to do is tell the ContractLoader
instance to scan the directory again. So, for those of you who are waiting to have DbC implemented in your favorite .NET language but are still waiting for Microsoft to implement it, there's no need to worry. All you have to do is to wait for Part V of this series and I'll show you how LinFu
does language-independent Design by Contract. Stay tuned!
History
- 15 November, 2007 -- Original version posted