Oct 21, 2009

Ninject 2.0 Contextual Binding

 

image There have been some minor changes to the contextual binding syntax. The new syntax looks like this:


kernel.Bind<IAttackAbility>().To<StrongAttack>().WhenTargetHas<StrongAttribute>();

My preferred approach to using contextual binding is to define a default binding first:

kernel.Bind<IAttackAbility>().To<UnknownAttack>();
kernel.Bind<IAttackAbility>().To<WeakAttack>().WhenTargetHas<WeakAttribute>();
kernel.Bind<IAttackAbility>().To<StrongAttack>().WhenTargetHas<StrongAttribute>();

If you don’t like attributes and never make typing mistakes (I suppose it’s also useful when you can’t modify the source code) there is also a name-based binding:

kernel.Bind<IAttackAbility>().To<StrongAttack>().When(c => c.Target.Name.StartsWith("Strong"));

Since one code snippet is worth 500 words here’s the entire unit test I wrote. This both demonstrates the conditional binding and starts to give you a flavor of how Ninject can simplify your code once the container wiring is done.

Code Snippet
  1. using System;
  2. using Microsoft.VisualStudio.TestTools.UnitTesting;
  3. using Ninject;
  4.  
  5. namespace TestNinject
  6. {
  7.     public class WeakAttribute : Attribute{}
  8.     public class StrongAttribute : Attribute {}
  9.  
  10.  
  11.     public interface IAttackAbility
  12.     {
  13.         int Strength { get; }
  14.     }
  15.  
  16.     public class UnknownAttack : IAttackAbility
  17.     {
  18.         public int Strength
  19.         {
  20.             get { return -1; }
  21.         }
  22.     }
  23.  
  24.     public class WeakAttack : IAttackAbility
  25.     {
  26.         public int Strength
  27.         {
  28.             get { return 1; }
  29.         }
  30.     }
  31.  
  32.     public class StrongAttack : IAttackAbility
  33.     {
  34.         public int Strength
  35.         {
  36.             get { return 10; }
  37.         }
  38.     }
  39.  
  40.     public interface IWeapon
  41.     {
  42.         string Name { get; }
  43.         IAttackAbility StrongAttack { get; set; }
  44.         IAttackAbility WeakAttack { get; set; }
  45.         IAttackAbility WtfAttack { get; set; }
  46.     }
  47.  
  48.     public class Sword : IWeapon
  49.     {
  50.         [Inject, Weak]
  51.         public IAttackAbility WeakAttack { get; set; }
  52.  
  53.         [Inject, Strong]
  54.         public IAttackAbility StrongAttack { get; set; }
  55.  
  56.         [Inject]
  57.         public IAttackAbility WtfAttack { get; set; }
  58.  
  59.         public string Name
  60.         {
  61.             get { return "sword"; }
  62.         }
  63.     }
  64.  
  65.     [TestClass]
  66.     public class GettingStartedTests
  67.     {
  68.         private readonly StandardKernel _kernel = new StandardKernel();
  69.  
  70.         public GettingStartedTests()
  71.         {
  72.             _kernel.Bind<Sword>().ToSelf().InSingletonScope();
  73.             _kernel.Bind<IWeapon>().ToMethod(c => c.Kernel.Get<Sword>());
  74.  
  75.             _kernel.Bind<IAttackAbility>().To<UnknownAttack>();
  76.             _kernel.Bind<IAttackAbility>().To<StrongAttack>().WhenTargetHas<StrongAttribute>();
  77.             _kernel.Bind<IAttackAbility>().To<WeakAttack>().WhenTargetHas<WeakAttribute>();
  78.         }
  79.  
  80.  
  81.         [TestMethod]
  82.         public void TestSingletonScope()
  83.         {
  84.             var sword1 = _kernel.Get<Sword>();
  85.             var sword2 = _kernel.Get<IWeapon>();
  86.             Assert.AreSame(sword1, sword2);
  87.         }
  88.  
  89.         [TestMethod]
  90.         public void TestContextualBinding()
  91.         {
  92.             var sword1 = _kernel.Get<IWeapon>();
  93.  
  94.             Assert.AreEqual(1, sword1.WeakAttack.Strength);
  95.             Assert.AreEqual(10, sword1.StrongAttack.Strength);
  96.             Assert.AreEqual(-1, sword1.WtfAttack.Strength);
  97.         }
  98.  
  99.         [TestMethod, ExpectedException(typeof(NullReferenceException))]
  100.         public void TestNoTargetThrows()
  101.         {
  102.             IAttackAbility my_attack_ability = _kernel.Get<IAttackAbility>();
  103.         }
  104.     }
  105. }

One Gotcha (well it got me anyway)

I’m in the process of refactoring an actual project towards Ninject so I’m probably doing some unusual things along the way. One thing I was doing was the equivalent of directly getting an IAttackAbility in code:

IAttackAbility my_attack_ability; = _kernel.Get<IAttackAbility>();

Prior to using a contextual binding (that is I only had IAttackAbility mapped directly to StrongAttack) this code would work fine. Once I add contextual binding this code will throw a NullReferenceException.  You can see a screencast demonstrating this behavior. I’m not sure I understand exactly why, but I’m surmising that once I add the WhenTargetHas clause, I must be injecting into a target that the Ninject context is already aware of. In this case Ninject doesn’t know anything about my_attack_ability and therefore when it tries to evaluate the WhenTargetHas clause it throws. 

 UPDATE- I have been communicating with Ian Davis and the gotcha behavior was a bug that he fixed on 10/22/2009. TestNoTargetThrows now fails, which is a good thing. I got rid of it and replaced it with the test below.

[TestMethod]
public void Can_get_attack_ability_without_target()
{
    IAttackAbility my_attack_ability = _kernel.Get<IAttackAbility>();
    Assert.AreEqual(-1, my_attack_ability.Strength);
}

I’m also experimenting with a new test_naming_convention.image

Related Posts

  1. Becoming a Ninject 2.0 Warrior.
  2. Passing Parameters

Readers' Most Popular Posts