Dependency injection across assemblies

When working at a project I came across a ground ripe for dependency injection. The current team had done a pretty good work of extracting interfaces, introducing a data access and a rudimentary service layer but never went the final step of actually introducing a dependency injection framework to handle the nitty gritty details. Dependency injection without an inversion of control container will at some point result in someone instantiating the implementing classes. Or a lot of factories. That won’t do.

I settled upon trying Ninject, since it looked easy enough to set up using my now-standard method of using NuGet for everything. Doing the default thing here will result in your MVC app being provided with an App_Start folder with a Ninject class that does the bootstrapping (courtesy of WebActivator, another nice library all in itself). In the end, you’ll end up with a IKernel on which to register your bindings using a pretty nice syntax:

kernel.Bind<IMyInterface>().To<MyImplementation>().InWhateverScopeYouWant();

Once bound, you’ll end up with a constructor injection pattern out-of-the box with each Controllers dependencies automatically resolved. Nice!

Given pretty much free choice over the dependency injection framework I was given one key rule, there is to be no dependencies to this framework outside of the assembly that contained the actual bootstrapping. Everything, all interfaces and all implementing classes, were contained in different assemblies.

Ninject gives you two solutions to this issue, you either reference every assembly from the bootstrapping project and go all out in with IKernel.Bind() in your bootstrapper, or you use a set of modules. Modules in Ninject are small classes that can outsource the binds to an assembly local implementing class. Neither of these solutions are good enough.

The first one, besides the tremendous amount of references to everything and everyone will result the implementing classes being public, since the bootstrapping will need to know their signature for the binding to work. I really want my interface-implementing classes to be internal, to avoid any “accidental” instantiation.

The second one is a lot better, having no such problems with the access modifier. However, it carries a requirement that each of the containing libraries will have to include a reference to Ninject. It’s not a huge library by any means, but a dependency is a dependency and if Ninject didn’t suit our needs it’d be pretty hard to replace.

So, we need a solution which can work across modules, and doesn’t carry a reference to the actual framework. This is what I came up with after some thought.

    using System;
    using System.Linq;
    using System.Reflection;

    public static class IoC
    {
        public enum Scope
        {
            Transient, Request, Singleton
        }

        private static IDependencyResolver container;

        public static T Get<T>()
        {
            return container.Get<T>();
        }

        public static void Initialize(IDependencyResolver c, bool autoLoadModules = true)
        {
            container = c;

            if (autoLoadModules)
            {
                // Find all assemblies and all classes implementing the IDependencyLocatorModule
                // for each of these, register the dependencies
                Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
                foreach (var assembly in assemblies)
                {
                    var q = from t in assembly.GetTypes()
                            where t.IsClass && typeof (IDependencyLocatorModule).IsAssignableFrom(t)
                            select t;
                    q.ToList().ForEach(t => LoadModule(t, container));
                }
            }
        }

        public static void LoadModule(Type type, IDependencyResolver resolver)
        {
            ConstructorInfo constructorInfo = type.GetConstructor(Type.EmptyTypes);
            if (constructorInfo == null)
            {
                // Log an error
            }
            else
            {
                var locatorModule = constructorInfo.Invoke(new object[] {}) as IDependencyLocatorModule;
                if (locatorModule == null)
                {
                    // Could not instantiate the locator module, log an error and continue
                }
                else
                {
                    locatorModule.RegisterDependencies(resolver);
                }
            }
        }
    }

    public interface IDependencyResolver
    {
        void RegisterDependency(Type interfaceType, Type concreteType, IoC.Scope scope = IoC.Scope.Transient);

        T Get<T>();
    }

    public interface IDependencyLocatorModule
    {
        void RegisterDependencies(IDependencyResolver resolver);
    }

This solution does quite a few things. IoC gives a class to hold a wrapped IKernel, since the rest of the code cannot, and must not use it directly. So far this container has seen very little use, but it’s there. The real magic happens in the Initialize method. Since we are in the startup project, in a sense, all the needed assemblies will have been loaded come the bootstrapping. Since we’ll place all this dependency injection stuff in it’s own separate library – and it’s all good old safe C# anyway, everyone can access this stuff.

So, every library that needs dependency injection needs to provide just one class. Place it anywhere you like in the project, make it public, implement the IDependencyLocatorModule and you get correct bindings without either a reference to any specific framework or compromising access restrictions.

The RegisterDependencies() method of IDependencyLocatorModule is really just a sample. Ninject contains a lot more functionality in itself than that, but since no one uses them yet I like to use the keep-it-simple argument at leave that until it’s actually needed. The wrapping itself of Ninject in a IDependencyResolver was dead simple, and it should not be a problem to use it no matter what framework you actually choose.

Advertisements
Tagged , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: