Skip to content
bartelink edited this page Mar 17, 2011 · 39 revisions

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. There are two primary reasons one might want to register multiple type bindings:-

1. Multiple bindings – for Multi-Injection

This mechanism allows you to get a set of related service implementations at the same time. You use this mechanism by requesting a IEnumerable<>, T[] array or List<T> in the target – see Multi-injection

class Warrior {
    readonly IEnumerable<IWeapon> _weapons;
    public Warrior( IEnumerable<IWeapon> weapons)
    {
        _weapons=weapons;
    }
    public void Attack(string victim) {
        foreach(var weapon in _weapons) 
            Console.WriteLine(weapon.Hit(victim));
    }
}

2. Multiple bindings – for conditional binding taking the context into consideration

In this case, the appropriate implementation that should be used depends on the target context for which the service type is being resolved and the binding metadata on each binding. Up until this point, we’ve only been talking about default bindings — bindings that are used unconditionally, in any context. However, unless you make one or more of the bindings conditional, you’ll get an exception at runtime indicating that the appropriate binding to use in a given context is ambiguous (you’ll run into the same case if you do a kernel.Get<T>() and there are multiple possible results for the given service type T).

Basic conditional binding – Named bindings

Named bindings are the simplest (and most common) form of conditional binding, and are used as follows:

  1. In your type bindings, you register multiple type bindings for the same service type, but add binding metadata so you’ll be able to indicate which one is appropriate in your condition later:-
Bind<IWeapon>().To<Shuriken>().Named("Strong");
Bind<IWeapon>().To<Dagger>().Named("Weak");

2. At the target location, you indicate the name you want to use in the given context:-

class WeakAttack {
    readonly IWeapon _weapon;
    public([Named("Weak") IWeapon weakWeapon)
        _weapon = weakWeapon;
    }
    public void Attack(string victim){
        Console.WriteLine(_weapon.Hit(victim));
    }
}

3. alternately, you can programmatically achieve the same effect directly (aka Service Location antipattern by doing:

kernel.Get<IWeapon>("Weak");

The old style

WIP: This remainder of this page represents v1 style and syntax. Contextual binding has been change quite drastically since v1. While the same things (and much more are possible, this is simply no longer a good way of describing or demonstrating the capabilities)

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 also used to evaluate methods for interception, and for declaring selection heuristics. More on those things 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:

bc.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|Injection 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:

bc.public class RangeAttribute : Attribute {}

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

bc.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:

bc.public class Ninja {
[Inject] public IWeapon MeleeWeapon { get; set; }
[Inject, Tag(“range”)] public IWeapon RangeWeapon { get; set; }
}

Then, your bindings would instead look like this:

bc.Bind().To();
Bind().To().Only(When.Context.Tag == “range”);

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