Contextual binding issues

82 views
Skip to first unread message

Rob A'Court

unread,
May 14, 2013, 2:17:07 PM5/14/13
to nin...@googlegroups.com
Hi

Say we have the following classes:

        private class Warrior
        {
            [Inject]
            public IWeapon Weapon { getset; }
        }
 
        interface IWeapon
        {
             
        }
 
        private class Harpoon : IWeapon
        {
        }
 
        private class Gun : IWeapon
        {
        }

We want contextual bindings so that we used Harpoons in the water and Guns on the land. We could achieve this with named bindings like so:

        kernel.Bind<IWeapon>().To<Harpoon>().Named(water);
        kernel.Bind<IWeapon>().To<Gun>().Named(land);

Which would work if we did this:

       var waterWeapon = kernel.Get<IWeapon>(water);

But we may want a Water Warrior. The Warrior class itself does not vary, the variation is what is injected into it. Unfortunately the following line does not work with the existing bindings:

       var waterWarrior = kernel.Get<Warrior>(water);

So we have to set up some additional bindings:

       kernel.Bind<IWeapon>().To<Harpoon>().Named(water);
       kernel.Bind<IWeapon>().To<Gun>().Named(land);
       kernel.Bind<IWeapon>().To<Harpoon>().WhenAnyAnchestorNamed(water);
       kernel.Bind<IWeapon>().To<Gun>().WhenAnyAnchestorNamed(land);

Although this may not look too bad here, it's doubling our bindings and in our real life code it's proving problematic.

Our bigger issue is that we also have conditions:

      kernel.Bind<IWeapon>().To<Harpoon>().Named(water);
      kernel.Bind<IWeapon>().To<Gun>().When(x => haveBullets).Named(land);
      kernel.Bind<IWeapon>().To<Crossbow>().When(x => !haveBullets).Named(land);

Named bindings don't work this way it would appear, they name the binding not place a condition upon it and so they would appear to be incompatible with Where clauses. Is that right?

So maybe instead of named bindings we should be using attributes and the WhenClassHas<> method. But I'm not sure how to do something equivalent to the WhenAnyAnchestorNamed method above (WhenClassOrAncestorHas?). I'm also not sure how they would work when calling Get<> directly, which at the moment we do have to do when we have complex construction inside Providers.

Any ideas?

Many thanks

Rob

Rob A'Court

unread,
May 17, 2013, 9:41:03 AM5/17/13
to nin...@googlegroups.com
My work mates have come up with a solution using attributes.

        public static IBindingInNamedWithOrOnSyntax<T> WhenTargetOrAncestorTargetHas<T>(this IBindingWhenSyntax<T> binding, Type type)
        {
            return binding.When(x => WhenAncestorTargetHas(x, type));
        }
 
        public static IBindingInNamedWithOrOnSyntax<T> WhenTargetOrAncestorTargetHas<T>(this IBindingWhenSyntax<T> binding, Type type, Func<IRequestbool> condition)
        {
            return binding.When(request => condition(request) && WhenAncestorTargetHas(request, type));
        }
 
        public static IBindingInNamedWithOrOnSyntax<T> WhenTargetHas<T>(this IBindingWhenSyntax<T> binding, Type type, Func<IRequestbool> condition)
        {
            return binding.When(request => condition(request) && request.Target.GetCustomAttributes(type,false).Length > 0);
        }
        
        private static bool WhenAncestorTargetHas(IRequest request, Type type)
        {
            while (request.Target != null)
            {
                var ancestorHas = request.Target.GetCustomAttributes(type, false).Length > 0;
 
                if (ancestorHas)
                    return true;
 
                else request = request.ParentRequest;
            }
 
            return false;
        }

They've also managed to work out a way of using these attributes even when using a ContextPreservingGet by creating a fake target.

You call:

    var waterWarrior = context.ContextPreservingGet<Warrior>(typeof(Water));

The code for this is:

    public static class NinjectTargetExtensions
    {
        public static T ContextPreservingGet<T>(this IContext context, Type attribute)
        {
            var target = new DummyTarget(typeof(T) , attribute);
 
            var request = new Request(context, typeof(T), target, null);
 
            var resolutions = context.GetContextPreservingResolutionRoot().Resolve(request).Cast<T>().ToList();
 
            if (resolutions.Count == 0)
                throw new ActivationException(ExceptionFormatter.CouldNotResolveBinding(request));
 
            if (resolutions.Count > 1)
                throw new ActivationException(ExceptionFormatter.CouldNotUniquelyResolveBinding(request));
 
            return resolutions[0];
        }
    }

    public class DummyTarget : ITarget
    {
        private readonly Type _targetType;
        private const string _name = "DummyTarget";
        private readonly DummyAttributeProvider _attributeProvider;
        private readonly Future<Func<IBindingMetadatabool>> _constraint;
        private readonly DummyMethodInfo _dummyMethodInfo;
 
        public DummyTarget(Type targetType,Type attributeType)
        {
            _targetType = targetType;
            _dummyMethodInfo = new DummyMethodInfo(attributeType);
            _attributeProvider = new DummyAttributeProvider(attributeType);
            _constraint = new Future<Func<IBindingMetadatabool>>(ReadConstraintFromTarget);
        }
 
        public object[] GetCustomAttributes(Type attributeType, bool inherit)
        {
            return _attributeProvider.GetCustomAttributes(attributeType, inherit);
        }
 
        public object[] GetCustomAttributes(bool inherit)
        {
            return _attributeProvider.GetCustomAttributes(inherit);
        }
 
        public bool IsDefined(Type attributeType, bool inherit)
        {
            return _attributeProvider.IsDefined(attributeType, inherit);
        }
 
        public object ResolveWithin(IContext parent)
        {
            if (Type.IsArray)
            {
                Type service = Type.GetElementType();
                return GetValues(service, parent).CastSlow(service).ToArraySlow(service);
            }
 
            if (Type.IsGenericType)
            {
                Type gtd = Type.GetGenericTypeDefinition();
                Type service = Type.GetGenericArguments()[0];
 
                if (gtd == typeof (List<>) || gtd == typeof (IList<>) || gtd == typeof (ICollection<>))
                    return GetValues(service, parent).CastSlow(service).ToListSlow(service);
 
                if (gtd == typeof (IEnumerable<>))
                    return GetValues(service, parent).CastSlow(service);
            }
 
            return GetValue(Type, parent);
        }
 
        protected virtual object GetValue(Type service, IContext parent)
        {
            var request = parent.Request.CreateChild(service, parent, this);
            request.IsUnique = true;
            return parent.Kernel.Resolve(request).SingleOrDefault();
        }
 
        protected virtual IEnumerable<object> GetValues(Type service, IContext parent)
        {
            var request = parent.Request.CreateChild(service, parent, this);
            request.IsOptional = true;
            return parent.Kernel.Resolve(request);
        }
 
        public Type Type
        {
            get { return _targetType; }
        }
 
        public string Name
        {
            get { return _name; }
        }
 
        public MemberInfo Member
        {
            get { return _dummyMethodInfo;  }
        }
 
        public Func<IBindingMetadatabool> Constraint
        {
            get { return _constraint; }
        }
 
        public bool IsOptional
        {
            get { return false; }
        }
 
        public bool HasDefaultValue
        {
            get { return false; }
        }
 
        public object DefaultValue
        {
            get { throw new NotImplementedException(); }
        }
 
        protected virtual Func<IBindingMetadatabool> ReadConstraintFromTarget()
        {
            var attributes = this.GetCustomAttributes(typeof (ConstraintAttribute), trueas ConstraintAttribute[];
 
            if (attributes == null || attributes.Length == 0)
                return null;
 
            if (attributes.Length == 1)
                return attributes[0].Matches;
 
            return metadata => attributes.All(attribute => attribute.Matches(metadata));
        }
    }

public class DummyMethodInfo : MethodInfo
    {
        private readonly Type _attributeType;
 
        public DummyMethodInfo(Type attributeType)
        {
            _attributeType = attributeType;
        }
 
        public override object[] GetCustomAttributes(bool inherit)
        {
            throw new NotImplementedException();
        }
 
        public override bool IsDefined(Type attributeType, bool inherit)
        {
            throw new NotImplementedException();
        }
 
        public override ParameterInfo[] GetParameters()
        {
            throw new NotImplementedException();
        }
 
        public override MethodImplAttributes GetMethodImplementationFlags()
        {
            throw new NotImplementedException();
        }
 
        public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
 
        public override MethodInfo GetBaseDefinition()
        {
            throw new NotImplementedException();
        }
 
        public override ICustomAttributeProvider ReturnTypeCustomAttributes
        {
            get { throw new NotImplementedException(); }
        }
 
        public override string Name
        {
            get { return "ContextPreservingGet"; }
        }
 
        public override Type DeclaringType
        {
            get { throw new NotImplementedException(); }
        }
 
        public override Type ReflectedType
        {
            get { return _attributeType; }
        }
 
        public override RuntimeMethodHandle MethodHandle
        {
            get { throw new NotImplementedException(); }
        }
 
        public override MethodAttributes Attributes
        {
            get { throw new NotImplementedException(); }
        }
 
        public override object[] GetCustomAttributes(Type attributeType, bool inherit)
        {
            throw new NotImplementedException();
        }
    }

    public class DummyAttributeProvider : ICustomAttributeProvider
    {
        private readonly Type[] _attribs;
 
        public DummyAttributeProvider(params Type[] attribs)
        {
            _attribs = attribs;
        }
 
        public object[] GetCustomAttributes(Type attributeType, bool inherit)
        {
            return _attribs.Where(x => x == attributeType).ToArray();
        }
 
        public object[] GetCustomAttributes(bool inherit)
        {
            return _attribs;
        }
 
        public bool IsDefined(Type attributeType, bool inherit)
        {
            return _attribs.Contains(attributeType);
        }
    }

It seems to work but we're interested to see if anyone else thinks this is a good solution?
Reply all
Reply to author
Forward
0 new messages