Saturday, April 7, 2012

Build a Pluggable Application with IoC Container


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 situation of we don’t want to directly hardcode the logic of instantiate an implementation of contract/interface in our application. By following contract first design principal and relay 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 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 based on mapping in configuration file to get specific implementation.
After got a collection of IOutputService, then 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.