Geeklog
There's no place like 127.0.0.1 - ramblings about programming

Advanced Object Builder - Dependency Injection

April 17, 2008 16:30 by The Geek

This a continuation from here, here, here, here and here.

The final feature of my object builder is dependency injection.

Dependency injection is useful as the class that has the dependency does not need to know about how to obtain an instance.  This is handy is many situations - especially in MVC where the controller is created by the view, but needs a service which supplies the model (and can't be supplied by the view).

I am only going to write about automatically setting property values - not setting constructor arguments (which I will leave for you to figure out ;-)).

Note: If you have been following my previous posts, sorry, but I have refactored some methods.  Some methods now take a Type argument rather than a generic type parameter.

The implementation is actually really simple, first we need a method of telling the object builder that it should inject a value into a property.  What could be easy than using custom attributes?

    interface IDependencyAttribute { }

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class PropertyDependencyAttribute
        : Attribute
        , IDependencyAttribute
    {
        public PropertyDependencyAttribute()
        {
        }
    }

Now that we have created an attribute, lets apply it to a new property in the Dog class.

        [PropertyDependency()]
        public ICat Eats
        {
            get { return this._eats; }
            set { this._eats = value; }
        }

Lets also change the consuming program:

    static void Main(string[] args)
    {
        IDog dog = Builder.Build<IDog>();
        Console.WriteLine("Legs: {0}, Talk: {1}.", dog.Legs, dog.Talk);
        Console.WriteLine("Legs: {0}, Talk: {1}.", dog.Eats.Legs, dog.Eats.Talk);

        Console.ReadLine();
    }

Obviously, this code will fail with a null reference exception because we have never set the Eats property.  Let's create a new class to handle dependency injection for us:

    internal static class DependencyInjector
    {
        /// <summary>
        /// Set the values of any properties marked with an <see cref="IDependencyAttribute"/>
        /// </summary>
        /// <param name="instance">The object to reflect upon</param>
        public static void InjectPropertyValues(object instance)
        {
            //get all the properties in the object that are public
            PropertyInfo[] pis = instance.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

            //loop through them
            foreach (PropertyInfo pi in pis)
            {
                //get any IDependencyAttributes applied to the property
                object[] atts = pi.GetCustomAttributes(typeof(IDependencyAttribute), true);

                //if we found one, the build an instance (of the type that the property requires) and assign it
                if (atts.Length > 0)
                {
                    //TODO: Arguments for the new object
                    pi.SetValue(instance, Builder.Build(pi.PropertyType), null);
                }
            }
        }
    }

As you can see, all we need to do is find all of the properties in the supplied object.  Once we have all of the propertes, we check if any of them have an IDependencyAttribute applied.  If they do, we build an instance of the same type as the property and set the property's value.  Everything is in place - all that is left is to call the InjectPropertyValues method:

    //build the object
    object instance = Build(toBuild, args);

    //inject dependencies
    DependencyInjector.InjectPropertyValues(instance);

    return (T)instance;

Note that I have moved the contents of the Builder.Build<T> method to a new method - Build - so that it can be called from the DependencyInjector.InjectPropertyValues method.

Thats it - now when we run the program, the dog will eat the cat without us ever setting the Eats property :-)

Source code: ObjectBuilder_DI.zip (90.08 kb)


Comments