Implementing basic Dependency Injection using a Service Container

By extending your Service Container class, a very basic version of dependency injection can be implemented. We'll implement two forms of dependency injection: constructor and property injection.

We'll start by defining the Injectable attribute.

[AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Property,
    AllowMultiple = false, Inherited = true)]
public class InjectableAttribute : Attribute
{
}

We'll use this attribute to mark our constructors and properties for dependency injection. Next we'll define an interface for our dependency injector:

public interface IDependencyInjector
{
    T Construct<T>();
    void Inject(object instance);
}

We'll define our service container like so:

public class ServiceContainer : IDependencyInjector, IServiceProvider
{
    Dictionary<Type, Object> services;

    public ServiceContainer()
        : base()
    {
        this.services = new Dictionary<Type, object>();
    }

    public void AddService(Type type, Object provider)
    {
        if(null == type)
            throw new ArgumentNullException("type");

        if(null == provider)
            throw new ArgumentNullException("provider");

        if(this.services.ContainsKey(type))
            throw new InvalidOperationException("A provider is already registered the type " + type);

        var providerType = provider.GetType();

        if(!type.IsAssignableFrom(providerType))
            throw new InvalidOperationException(providerType + " is not an instance of " + type);

        this.services.Add(type, provider);
    }

    public object GetService(Type type)
    {
        if(null == type)
            throw new ArgumentNullException("type");

        if(this.services.ContainsKey(type))
            return this.services[type];
                    
        return null;
    }

    public void RemoveService(Type type)
    {
        if(null == type)
            throw new ArgumentNullException("type");

        this.services.Remove(type);
    }

    protected object GetInjectableService(Type type)
    {
        if(type == typeof(IDependencyInjector) ||
           type == typeof(IServiceProvider))
        {
            return this;
        }
        else
        {
            object service = this.GetService(type);

            if(service == null)
                throw new InvalidOperationException("Failed to find " + type + " depenedency.");

            return service;
        }
    }

    public T Construct<T>()
    {
        ConstructorInfo injectableConstructor = null;
        foreach(ConstructorInfo constructor in typeof(T).GetConstructors())
        {
            foreach(Attribute attribute in constructor.GetCustomAttributes(true))
            {
                if(attribute is InjectableAttribute)
                {
                    injectableConstructor = constructor;
                    break;
                }
            }

            if(injectableConstructor != null)
                break;
        }

        if(injectableConstructor == null)
            throw new InvalidOperationException("No injectable constructor found.");

        var parameters = injectableConstructor.GetParameters();
        var services = new object[parameters.Length];

        int i = 0;
        foreach(ParameterInfo parameter in parameters)
            services[i++] = GetInjectableService(parameter.ParameterType);

        return (T)injectableConstructor.Invoke(services);
    }

    public void Inject(object instance)
    {
        foreach(PropertyInfo property in instance.GetType().GetProperties())
        {
            foreach(Attribute attribute in property.GetCustomAttributes(true))
            {
                if(attribute is InjectableAttribute)
                {
                    if(!property.CanWrite)
                        throw new InvalidOperationException(property.Name + " is marked as Injectable but not writable.");

                    property.SetValue(instance, GetInjectableService(property.PropertyType), null);
                }
            }
        }
    }
}

You can now construct new instances and inject dependencies on existing instances. Some usage examples:

public interface IFoo
{
    int Value { get; }
}

public class Foo : IFoo
{
    public int Value
    {
        get;
        set;
    }

    [Injectable]
    public Foo()
    {
    }

    public void DoIt()
    {
        Console.WriteLine(this.Value);
    }
}

public interface IBar
{
    string Value { get; }
}

public class Bar : IBar
{
    IFoo foo;

    public string Value
    {
        get;
        set;
    }

    [Injectable]
    public Bar(IFoo foo)
    {
        this.foo = foo;
    }

    public void DoIt()
    {
        Console.WriteLine(this.Value + ": " + this.foo.Value);
    }
}

public class Baz
{
    [Injectable]
    public IFoo Foo
    {
        get;
        set;
    }

    [Injectable]
    public IBar Bar
    {
        get;
        set;
    }
                            
    public void DoIt()
    {
        Console.WriteLine(this.Bar.Value + " | " + this.Foo.Value);
    }
}

class Program
{
    static void Main(string[] args)
    {
        var container = new ServiceContainer();

        var foo = container.Construct<Foo>();
        foo.Value = 5;
        container.AddService(typeof(IFoo), foo);

        var bar = container.Construct<Bar>();
        container.AddService(typeof(IBar), bar);
        bar.Value = "Hello World!";
        bar.DoIt();

        var baz = new Baz();
        container.Inject(baz);
        baz.DoIt();
    }
}