Friday, 17 October 2008

Isolating your Dependency Injection Container

Hello,

While searching for some background information about Dependency Injection Frameworks (aka Ioc containers ; ex. Windsor Castle, Unity, Spring.NET , StructureMap, etc) I stumbled on this posting by Chris Tavares (Unity creator) regarding the creation of a isolation layer for DI containers.

Because there are several DI containers available , the creation of an extra shield between your code and the DI container of your choice seems not a bad idea if you want to keep a maximum of freedom to swith from container to container somewhere in time. It could be that in the lifetime of an application another DI container might be more suitable for your problem at hand because of certain feature it has.

This is made possible with a generic interface that makes up the operations you can call from a DI container. So your code, when it needs a service from a DI container, only works against this interface. At application startup you of course need to hook up a real DI Container implementation with its own configuration semantics. For several DI containers ther are already adapters written that convert the generic API semantics to the DI container specific API calls.

You call read about it and download the code from the codeplex site.


Maybe some code snippets to get your appetite perhaps started?

In the following code snippet you see the creation of the Castle Windsor Container and the act of resolving an instance of a particular type.



class Program
{
static void Main(string[] args)
{
try
{
IWindsorContainer windsorContainer = CreateWindsorContainer();

ObjectCatalog.IMyObject obj = windsorContainer[typeof(ObjectCatalog.IMyObject)] as ObjectCatalog.IMyObject;

System.Console.WriteLine("Straight Windsor");
System.Console.WriteLine(obj.WhoAreYou());
System.Console.ReadLine();
}
catch (Exception ex)
{
System.Console.WriteLine(ex.Message);
System.Console.ReadLine();
}
}

private static IWindsorContainer CreateWindsorContainer()
{
IWindsorContainer windsorContainer;
windsorContainer = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
return windsorContainer;
}
}


The Castle Windsor config section looks like this

ps: I'm using SyntaxHighLighter. The XML is not shown correctly. It always set to uppercase. I will need to look it to that. (Tips are welcome!)





name="castle"
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />



id="idIMyObject"
service="ObjectCatalog.IMyObject, ObjectCatalog"
type="ObjectCatalog.MyObject, ObjectCatalog" />





Consequently your code is pretty heavily "connected" to the DI container implementation of Castle Windsor in variuos ways (code + config). Which isn't necessarly all bad because in the beginning of the project you choose Windsor for its abilities. But suppose you want (or you need ; consolidation, standardization , features , ...) to change to another DI container implementation

Let's rewrite everything to the Microsoft P'n'P Unity DI container.



class Program
{
static void Main(string[] args)
{
try
{

IUnityContainer unityContainer = CreateUnityContainer();

ObjectCatalog.IMyObject obj = unityContainer.Resolve();

System.Console.WriteLine("Straight Unity");
System.Console.WriteLine(obj.WhoAreYou());
System.Console.ReadLine();
}
catch (Exception ex)
{

System.Console.WriteLine(ex.Message);
System.Console.ReadLine();
}

}

private static IUnityContainer CreateUnityContainer()
{
IUnityContainer unityContainer;
unityContainer = new UnityContainer();
UnityConfigurationSection section = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
section.Containers["containerUnity"].Configure(unityContainer);
return unityContainer;
}
}


The config file for Unity for the example looks like this























So besides the config information and the creation of the container, you also must change every location where you use the services of the DI container (that's usually more than than one like in the example:-)

This is where the ServiceLocator comes in. It offers a generic way to talk to your DI container. The only implementation specifics are the config information and the creation of the container. So if you would need to change one day from one DI container to another, the changes kept to a minimum (although changing a DI config info from one format to another is also not to be taken light. Maybe some XSL magic !)

So how would the example look with the ServiceLocator?



class Program
{
static void Main(string[] args)
{
string ioc ="" ;
if (args.Length==0)
{ioc="Windsor";}
else
ioc=args[0] ;

try
{

IServiceLocator myLocator = CreateLocator(ioc);

ObjectCatalog.IMyObject obj = myLocator.GetInstance();

System.Console.WriteLine("Service Locator");
System.Console.WriteLine(" Implementation : " + myLocator.GetType().ToString());
System.Console.WriteLine(obj.WhoAreYou());
System.Console.ReadLine();
}
catch (Exception ex)
{

System.Console.WriteLine(ex.Message);
System.Console.ReadLine();
}
}

private static IServiceLocator CreateLocator(string ioc)
{
IServiceLocator myLocator=null;
if (ioc=="Unity")
myLocator = new UnityServiceLocator( CreateUnityContainer());
else if (ioc=="Windsor")
myLocator = new WindsorServiceLocator(CreateWindsorContainer());
else
throw new System.ArgumentException("Only Unity or Windsor are valid arguments");

return myLocator;
}

private static IWindsorContainer CreateWindsorContainer()
{
IWindsorContainer windsorContainer;
windsorContainer = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
return windsorContainer;
}
private static IUnityContainer CreateUnityContainer()
{
IUnityContainer unityContainer;
unityContainer = new UnityContainer();
UnityConfigurationSection section = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
section.Containers["containerUnity"].Configure(unityContainer);
return unityContainer;
}
}



So your code now makes use of a DI container generic API in order to hold on to a reference to a concrete DI Container implementation and also the calls to resolve dependencies or get instances from the DI container are written in a generic manner.
The DI Container specific calls are made in the adapters (one for each concrete DI Container). So "GetInstance" will be translated to "Resolve" for the Unity Container.

Thanks for reading.

Best regards,

Alexander

No comments: