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();
}
}