Contextual Binding

One of the more powerful (and complex) features of Ninject is its contextual binding system. We mentioned earlier that you can register more than one binding for a type. Up until this point, we've only been talking about default bindings – bindings that are used unconditionally, in any context. There's another kind of binding that's available, a conditional binding. As is suggested by its name, this sort of binding has a condition associated with it. This condition (represented by the ICondition<T> interface) examines the context in which the activation is occurring, and decides whether or not the binding should be used. Through the use of conditional bindings, you can design pretty much any sort of binding scheme you can dream up.

As you no doubt noticed, the ICondition<T> interface is generic, meaning it can be used to test things other than contexts. It's now also used to evaluate methods for interception. More on that later. Right now, we're only talking about examining the activation context, and the interface is really simple. Here it is:

interface ICondition<T> {
  bool Matches(T obj);
}

Conditions that test the activation context are generally created via the fluent interface, rooted in the When class in Ninject.Conditions. Since not all projects will require conditional binding, this namespace is actually stored in a separate assembly from Ninject.Core. To use it in your project you'll need to first add a reference to it. Here's an example of the fluent interface:

When.Context.Service.Name.StartsWith("Foo");

This condition will resolve to true when the service's type name starts with "Foo".

To define a conditional binding, the two fluent interfaces (binding and condition) work together. Here's an example:

Bind<IWeapon>().To<Sword>();
Bind<IWeapon>().To<Shuriken>().Only(When.Context.Target.HasAttribute<RangeAttribute>());

With these two bindings defined, whenever an instance of IWeapon is requested, by default, Ninject will activate an instance of Sword. However, if the target that is being injected is decorated with a [Range] attribute, Ninject will activate an instance of Shuriken instead.

The [Range] attribute is just a simple attribute that you create yourself, and is used as a marker for Ninject:

public class RangeAttribute : Attribute {}

Then, you can create two different implementations of a warrior, like so:

public class Swordsman {
  [Inject] public IWeapon Weapon { get; set; }
}

public class Ninja {
  [Inject] public IWeapon MeleeWeapon { get; set; }
  [Inject, Range] public IWeapon RangeWeapon { get; set; }
}

If you'd rather not create your own attributes, Ninject supplies a [Tag] attribute that you can use instead:

public class Ninja {
  [Inject] public IWeapon MeleeWeapon { get; set; }
  [Inject, Tag("range")] public IWeapon RangeWeapon { get; set; }
}

However, since string-based identifiers can be prone to typing mistakes, it's usually a good idea to just create the attributes yourself.

Continue reading: Conventions-Based Binding

Labels

 
(None)
  1. Aug 09

    Itzik Kasovitch says:

    Hi,...

    Hi,

    I have the following classes and interfaces: 
    public class DriverDisplayer
    {
            private Driver _driver1;
            private Driver _driver2;

            [Inject]
            public DriverDisplayer(Driver driver1,
                Driver driver2)
            

    Unknown macro: {                           _driver1 = driver1;               _driver2 = driver2;                }

            public void Display()
            {
                Console.WriteLine("Hello from

    Unknown macro: {0}

    ", _driver1.Name);
                Console.WriteLine("Hello from

    ", _driver2.Name);
            }
        }

        public interface Driver
        {
            string Name

    Unknown macro: { get; }

        }

        public class Driver1 : Driver
        {
            public string Name
            {
                get

    Unknown macro: { return "Driver1"; }

            }

        }

        public class Driver2 : Driver
        {
            public string Name
            {
                get

    Unknown macro: { return "Driver2"; }

            }

        }

    1) How should I configure the kernel so that injection succeeds?

    2) If the DriverDisplayer constructor receives an array of drivers, or an IList how it will be done then?

    1. Aug 12

      Minimal says:

      1) It would probably look something like this: internal class DriverModule : St...

      1) It would probably look something like this:

      internal class DriverModule : StandardModule
      
      {
          public override void Load()
          {
              Bind<DriverDisplayer>().ToSelf();
              Bind<Driver>().To<Driver1>().Only(When.Context.Target.Name.StartsWith("driver1"));
              Bind<Driver>().To<Driver2>().Only(When.Context.Target.Name.StartsWith("driver2"));
          }
      }


      2) What would be the purpose of that? How would you want to instantiate such a collection/array?

      Are you sure you understand the purpose of IoC containers? I'm asking because your examples are rather odd. I'd recommend reading "Inversion of Control Containers and the Dependency Injection pattern" by Martin Fowler (http://www.martinfowler.com/articles/injection.html). There's also quite a nice presentation by Justin Etheredge available on his blog (http://www.codethinked.com/post/2008/04/Dependency-Injection-Presentation.aspx). His examples may seem very simple but he uses both Ninject and Spring.NET in them so they might be very helpful.

Creative Commons License