Introduction
When we build an application, we always face a lot of changes during and after application development. To be ready for changes in advance is a key character of a flexible application. IoC container stands for Inversion of Control container. It is used frequently in situations where we don’t want to directly hardcode the logic of instantiate an implementation of contract/interface in our application. By following contract first design principle and relaying the instantiation responsibility to IoC container, we can create an extensible and flexible application.
Demo Application
Suppose we want to design an application that can output text to different output channel, such as file output channel and console output channel. What we can do first is to define an output contract, so the main application will follow the contract to output text without knowing specific output channel.
public interface IOutputService
{
void WriteLine(string data);
}
After we have the contract defined, we can have two different implementations as file output channel and console output channel:
FileOutput
:
public class FileOutput: IOutputService
{
public void WriteLine(string data)
{
using (StreamWriter sw = new StreamWriter("test.txt", false))
{
sw.WriteLine(data);
}
}
}
ConsoleOutput
:
public class ConsoleOutput: IOutputService
{
public void WriteLine(string data)
{
Console.Write(data);
}
}
The easiest way to get an output channel in application is to have a channel factory that will create an instance of requested output channel. However, by doing this, we hardcode the instantiation logic inside our application. If we want to add a new output channel, such as network output channel, then our only opinion is to change the code.
Make the Demo Application Pluggable
With the help of IoC container, we can easily create a pluggable application without much coding. The idea is to define a base library that will have an API like this:
public interface IIoCContainer
{
T Resolve<t>();
IEnumerable<t> ResolveAll<t>();
}
This API gives application a way to retrieve a single instance or a collection of instances of specified type, so you can use the IOutputService
interface
to get a specific output channel. The IoC container API allows us to use any IoC container, such as Unity
or StructureMap. At the moment of application starting up, we use BootStrapper
to setup IoC container.
BootStrapper
:
public class BootStrapper
{
public static void StartUp()
{
RegisterIoCContainer();
}
private static void RegisterIoCContainer()
{
ObjectFactory.Initialize(x => { x.PullConfigurationFromAppConfig = true; });
IoC.IoCContainerManager.IoCContainer = new StructureMapIoCContainer();
}
}
Initialize IoC container with BootStrapper
.
BootStrapper.StartUp();
After IoC container is initialized, application can directly use IoC container API to get IOutputService
.
IEnumerable<ioutputservice> outputServices =
IoC.IoCContainerManager.IoCContainer.ResolveAll<ioutputservice>();
outputServices.ToList().ForEach(o => o.WriteLine("Hello World!"));
In order to make our application more flexible and allow us to switch different output channel in production environment without recompilation, I decided to not directly reference output channel implementation in main application.
The output channel implementation will be copied into main application via Post-build event command line.
And the mapping between IOutputService
and actual implementation is defined in application configuration file.
<configSections>
<section name="StructureMap"
type="StructureMap.Configuration.StructureMapConfigurationSection,StructureMap"/>
</configSections>
<StructureMap>
<PluginFamily Type="OutputService.IOutputService"
Assembly="OutputService" DefaultKey="Default">
<Plugin Type="FileOutput.FileOutput"
Assembly="FileOutput" ConcreteKey="Default"/>
<Plugin Type="ConsoleOutput.ConsoleOutput"
Assembly="ConsoleOutput" ConcreteKey="Console"/>
</PluginFamily>
</StructureMap>
How the Whole Application Works
When calling IoCContainer.ResolveAll<ioutputservice>()
, the application will relay the call to IoC container, then IoCContainer
will be based on mapping in configuration file to get specific implementation.
After getting a collection of IOutputService
, call WriteLine
of IOutputService
to write “Hello World
” to every output channel.
Summary
With the help of IoC container, we can quickly implement a simple pluggable application. This pluggable application can utilize configuration file to add new implementation for defined contract without changing code.
Using the Code
The code is developed in Visual Studio 2010. You can create your own IOutputService
implementation and add into configuration file to experiment with the demo application.