Apr 11 2009

Inverting my control

Although not a new pattern the Alt.NET Community has been cheerleading Inversion of Control (IoC) for a few years now. Generally speaking, I think this has been good for all of us. This has reminded us old-timers that tightly coupled architectures are just not good for us, not to mention teaching this lesson to the noobs. Also good, has been the development of many community tools like Sprint.Net, Castle, and others, each of which help us (re)embrace this pattern.  

Whenever this topic comes up it is inevitably linked with Test Driven Development (TDD). This is unfortunate because no matter how you look at it people seem the miss the point of both when linking the two so closely together. Now that I have said that, this debate is a post for another day. Lets get back to IoC... I have tried working with a number of different open-source tools to implement IoC-based solutions. Some work very well, although the good ones have become so large, it begs the question. Do I really want to absorb such a large code-base for small projects? For me the answer is no, and since I am not willing to abandon the IoC pattern I had to write my own.

If you think about it, IoC is just a Factory pattern so creating one to map an interface to a concrete implementation will be easy. Here is what we need:

  • A Factory to resolve interfaces to concrete implementations
    • To give us some flexibility and separate the areas of concern we will also want to separate the actual resolution process so we can plugin new one's (Ex. Castle) as your products mature
  • Configuration Section to tell the factory how to map types

Lets start with an interface to define the contract for our resolver component. This will be responsible for understanding any one mapping implementation (Ex. Configuration-based, discovered via reflection, etc...). This can also be used to connect you to other containers like Castle at a later date, in case you change your mind and don't want to recode everything.

public interface IDependencyResolver
{
    T Resolve();
}

Now we need to create at least one implementation of the resolver.

using System;
using System.Collections.Generic;
using System.Configuration;

public sealed class DependencyResolver : IDependencyResolver
{
    private readonly Dictionary types = new Dictionary();

    public DependencyResolver()
    {
        MyCustomConfigSection configuration = MyCustomConfigSection.Current;

        foreach (ComponentConfigElement component in configuration.Components)
        {
            try
            {
                Register(Type.GetType(component.Contract, true, true), 
                Type.GetType(component.Implementation, true, true));
            }
            catch (Exception ex)
            {
                string message = "A configured component is not valid. Contract='{0}', Implementation={1}";
                message = string.Format(message, component.Contract, component.Implementation);

                var configurationErrorsException = new ConfigurationErrorsException(
                    message, ex);

                throw configurationErrorsException;
            }
        }
    }

    #region IDependencyResolver Members

    public T Resolve()
    {
        return (types.ContainsKey(typeof(T))) ? 
                    (T)Activator.CreateInstance(types[typeof(T)]) : default(T);
    }

    #endregion

    public void Register(Type contractType, Type implementationType)
    {
        if (contractType.IsAssignableFrom(implementationType) == false)
        {
            throw new InvalidOperationException(string.Format(
                "The supplied instance does not implement {0}", contractType.FullName));
        }

        if (types.ContainsKey(contractType))
            types.Remove(contractType);

        types.Add(contractType, implementationType);
    }
}

Now lets create the Factory class. This will serve user requests for resolution and will load a configured resolver.

using System;

public static class IoC
{
    private static IDependencyResolver _resolver;

    static IoC()
    {
        Initialize();
    }

    private static void Initialize()
    {
        MyCustomConfigSection configuration = MyCustomConfigSection.Current;

        if (configuration == null || string.IsNullOrEmpty(configuration.Components.Resolver))
        {
            var exception = new ConfigurationErrorsException(
                "No IoC resolver configuration found in the configuration file");

            throw exception;
        }

        Type configuredResolver = Type.GetType(configuration.Components.Resolver, false, true);

        if (configuredResolver == null)
        {
            var exception = new ConfigurationErrorsException(
                "The IoC resolver found in the configuration file is either an invalid type or could not be found");

            throw exception;
        }

        Initialize((IDependencyResolver)Activator.CreateInstance(configuredResolver));
    }

    public static void Initialize(IDependencyResolver resolver)
    {
        _resolver = resolver;
    }

    public static T Resolve()
    {
        return _resolver.Resolve();
    }
}

If we skip the configuration details, lets see how callers would get class instances using the Factory:

IPhoneNumber phoneNumber = IoC.Resolve<IPhoneNumber>();

Remember, this pattern forces us to think in terms of the contract represented by the Interface (e.g. contract/specification) as opposed to thing what any one concrete implementation provides you. Thats nice, but the reality is that it will never work without configuration. Here is a list of what we will need:

  • Configuration Element defining a mapping between an interface and an implementing class
  • Configuration Element Collection to contain a list of mappings

First the config element for the mappings:

using System.Configuration;

public sealed class ComponentConfigElement : ConfigurationElement
{
    public ComponentConfigElement()
    {
        Init();
    }

    #region Contract Property

    internal const string ContractPropertyName = "contract";

    [ConfigurationProperty(ContractPropertyName,
        Options = ConfigurationPropertyOptions.IsRequired ^ ConfigurationPropertyOptions.IsKey)]
    public string Contract
    {
        get { return (string)this[ContractPropertyName]; }
    }

    #endregion

    #region Implementation Property

    internal const string ImplementationPropertyName = "implementation";

    [ConfigurationProperty(ImplementationPropertyName, Options = ConfigurationPropertyOptions.IsRequired)]
    public string Implementation
    {
        get { return (string)this[ImplementationPropertyName]; }
    }

    #endregion
}

The following represents a collection of the previous mappings and provides for the configuration of a resolver implementation:

using System.Configuration;

public sealed class ComponentConfigElementCollection : ConfigurationElementCollection
{
    public override ConfigurationElementCollectionType CollectionType
    {
        get { return ConfigurationElementCollectionType.BasicMap; }
    }

    internal const string ElementPropertyName = "component";

    protected override string ElementName
    {
        get { return ElementPropertyName; }
    }

    protected override ConfigurationElement CreateNewElement()
    {
        return new ComponentConfigElement();
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((ComponentConfigElement)element).Contract;
    }

    #region Resolver Property

    internal const string ResolverPropertyName = "resolver";

    [ConfigurationProperty(ResolverPropertyName)]
    public string Resolver
    {
        get { return (string)this[ResolverPropertyName]; }
    }

    #endregion
}

These classes allow us to add the following to any config section you may have already defined for your application:

<components resolver="MyAssembly.DependencyResolver, MyAssembly">
    <component contract="MyAssembly.IPhoneNumber, MyAssembly" implementation="MyAssembly.PhoneNumber, MyAssembly" />
</components>

Well that's it...  This is pretty slim and can be extended to do a wide range of things. In fact, if you are following a Dependency Injection Pattern with your constructors this pattern is a VERY nice fit, but that's a Post for another day.  Enjoy.

If you would like a full set of source code, feel free to download the SmellyContainer, which will a full demonstration of this pattern.

Tags: ,