Posterous theme by Cory Watilo

NInject vs. StructureMap vs. I Made This

As I've mentioned before my team is working on a large scale ASP.NET MVC application and we're using dependency injection to decouple our data access layer from the application. Initially we were using NInject as our IoC tool of choice. It's lightweight and has a really nice configuration DSL and it's pretty fast. The problem arose when trying to get NInject's scope caching to work. It has this nifty feature where it will cache the objects it creates and you could specify the scope of that caching...for example SingletonBehavior, or OnPerSessionBehavior. That's great but I couldn't make it work, regardless of the configuration it would always return a brand new object every time. So then I went and checked out StructureMap. It's a little more complex to get set up and I personally don't think it's registration DSL is as nice as NInject's but it works. It has a similar caching feature where you can specify an Instance Scope of Application, Session, Request, Thread, etc. Same problem though, no matter how I configured it the caching was always at the Application level. So every session in the app would get back the same objects...not very secure on the data front. Now, I am completely open to the possibility that I don't know enough about either of these tools and that the roadblocks I ran into were user error. However, I realized I was spending way too much time on something that is essentially a very simple problem. Here's what I needed out of an IoC tool: 1. Constructor Injection - I don't really care about property or method injection, just constructors 2. Cache the Output at Session Level 3. Easy Registration with Constructor Args So the following is what I came up with to solve the problem:
namespace DI {

    internal class InjectionBinder {
        public Type Abstract { get; set; }
        public Type Concrete { get; set; }
        public object[] CtorArgs { get; set; }
    }

    /// 
    /// A lightweight dependency injection tool that supports constructor injection and HttpContext.Session caching.
    /// 
    public static class Injector {

        private const string SessionBindingKey = "__InjectorBindings__";
        private const string SessionCacheKey = "__InjectorCache__";

        private static List _bindings {
            get {
                if (HttpContext.Current.Session[SessionBindingKey] == null)
                    HttpContext.Current.Session[SessionBindingKey] = new List();
                return (List)HttpContext.Current.Session[SessionBindingKey];
            }
            set { HttpContext.Current.Session[SessionBindingKey] = value; }
        }

        private static List> _cache {
            get {
                if (HttpContext.Current.Session[SessionCacheKey] == null)
                    HttpContext.Current.Session[SessionCacheKey] = new List>();
                return (List>)HttpContext.Current.Session[SessionCacheKey];
            }
            set { HttpContext.Current.Session[SessionCacheKey] = value; }
        }

        /// 
        /// Returns a concrete instance of the specified abstract type using the provided bindings and 
        /// attempts to satisfy that type's constructor arguments.
        /// 
        /// The abstract type of the object to initialize.
        /// 
        public static object GetInstance(Type abstractType) {

            // Return the item from the cache if it's in there
            var cache = _cache.ToDictionary(o => o.Key, o => o.Value);
            if (cache.ContainsKey(abstractType))
                return cache[abstractType];

            // If the type is in the bindings, return the concrete
            var foundBinding = _bindings.Find(b => b.Abstract == abstractType);
            if (foundBinding != null)
                return Activator.CreateInstance(foundBinding.Concrete, foundBinding.CtorArgs);

            object obj = null;
            var ctors = abstractType.GetConstructors().ToList();

            // If the type has no constructor, then just create one
            if (ctors.Count == 0)
                return Activator.CreateInstance(abstractType);
            
            // If there are constructors, find one which takes arguments of the types we have binders for
            ctors.ForEach(c => {
                var prms = c.GetParameters().ToList();
                var args = new List();
                var haveBindings = true;
                prms.ForEach(p => {
                    var binding = _bindings.Find(b => b.Abstract == p.ParameterType);
                    if (binding == null) { haveBindings = false; }
                    else { args.Add(Activator.CreateInstance(binding.Concrete, binding.CtorArgs)); }
                });
                if (haveBindings) { obj = Activator.CreateInstance(abstractType, args.ToArray()); }
            });

            return obj;
        }

        /// 
        /// Returns a concrete instance of the specified abstract type using the provided bindings and 
        /// attempts to satisfy that type's constructor arguments.
        /// 
        /// The abstract type of the object to initialize.
        /// 
        public static TAbstract GetInstance() where TAbstract : class {
            return GetInstance(typeof (TAbstract)) as TAbstract;
        }

        /// 
        /// Creates a binding rule between an abstract type and a concrete type.
        /// 
        /// The abstract type for the binding.
        /// The concrete type for the binding
        /// The list of constructor arguments to use when initializing the instance of the concrete type.
        public static void Bind(object[] ctorArgs) {
            var binding = _bindings.Find(b => b.Abstract == typeof(TAbstract));
            if (binding != null) {
                binding.Concrete = typeof(TConcrete);
                binding.CtorArgs = ctorArgs;
            } else {
                _bindings.Add(new InjectionBinder {
                    Abstract = typeof(TAbstract),
                    Concrete = typeof(TConcrete),
                    CtorArgs = ctorArgs
                });
            }
        }

        /// 
        /// Clears all Injector binding rules and empties the internal cache.
        /// 
        public static void Kill() {
            HttpContext.Current.Session[SessionBindingKey] = null;
            HttpContext.Current.Session[SessionCacheKey] = null;
        }
    }
}
And here's how we register the bindings:
private static void BindInjectionContainer() {
    Injector.Kill(); // Clears out previously created bindings and empties the cache

    var currentClient = new Client(); // Doesn't matter...just an example
    var currentUser = new User();
    var currentCulture = "en-US";

    Injector.Bind(new object[] { currentClient, currentCulture });
    Injector.Bind(new object[] { currentClient, currentCulture, currentUser });
    Injector.Bind(new object[] {});
}
And lastly, here's how you request an object:
var secRepo = Injector.GetInstance();
So, it may be ridiculous to roll-your-own on the IoC container but as you can see it was pretty simple to get what I needed. It's very solution specific but what's wrong with that. In this instance it's pretty easy to swap out for something more feature rich if necessary but for now...I call YAGNI on all that.
| Viewed
times | Favorited 0 times

3 Comments

Mar 21, 2010
Sara said...
I know this post is old, but there was someone else who was having the same issue with Ninject. It was due to something not being configured correctly in the person's code. The answer is in the google forum for Ninject. I will find the link and post it here so that anyone else who stumbles across your blog will be able to fix this issue very quickly. The fix was something so simple that you will likely laugh about it now.
Jul 05, 2010
JEROME said...
PillSpot.org. Canadian Health&Care.Best quality drugs.Special Internet Prices.No prescription online pharmacy. No prescription drugs. Order drugs online...

Buy:Viagra Professional.Tramadol.Levitra.Super Active ED Pack.Viagra.Viagra Super Force.Cialis Professional.Cialis Super Active+.Viagra Super Active+.Propecia.VPXL.Viagra Soft Tabs.Cialis Soft Tabs.Maxaman.Soma.Cialis.Zithromax....

Jul 29, 2010
Pharmf895 said...
Hello! feadaef interesting feadaef site!

Leave a comment...