-
Notifications
You must be signed in to change notification settings - Fork 529
Optional Bindings and Null Object Pattern
By default, ninject StandardKernel
will throw an ActivationException
in case one requests a type where no binding is available and which is not self bindable. For example:
public interface IWeapon
{
void Strike();
}
public class Warrior
{
private readonly IWeapon weapon;
public Warrior(IWeapon weapon)
{
this.weapon = weapon;
}
public void Attack()
{
this.weapon.Strike();
}
}
public class Demo
{
[Fact]
public void Demonstrate()
{
var kernel = new StandardKernel();
kernel.Bind<Warrior>().ToSelf();
kernel.Get<Warrior>();
}
}
Here, kernel.Get<Warrior>()
will throw:
Error activating IWeapon
No matching bindings are available, and the type is not self-bindable.
Activation path:
2) Injection of dependency IWeapon into parameter weapon of constructor of type Warrior
1) Request for Warrior
Suggestions:
1) Ensure that you have defined a binding for IWeapon.
2) If the binding was defined in a module, ensure that the module has been loaded into the kernel.
3) Ensure you have not accidentally created more than one kernel.
4) If you are using constructor arguments, ensure that the parameter name matches the constructors parameter name.
5) If you are using automatic module loading, ensure the search path and filters are correct.
You can tell ninject to not throw, but inject null values instead. This is done by setting AllowNullInjection
on the NinjectSettings
to true:
var kernel = new StandardKernel(
new NinjectSettings
{
AllowNullInjection = true
});
However, we now have to adapt the implementation of Warrior.Attack()
to:
public void Attack()
{
if (this.weapon != null)
{
this.weapon.Strike();
}
}
Or Attack()
ing would fail with a NullReferenceException
.
But hold on, the null object pattern is coming to your rescue!
(Also see Wikipedia)
With the null object pattern, instead of handling null
s all over the place, you are creating a "null" implementation of the interface instead:
public class NullWeapon : IWeapon
{
public void Strike()
{
// i'm the lazy null! I'm really not doing anything but watching 0s and 1s pass by all day long, imagine what a happy life!
}
}
now add a binding:
kernel.Bind<IWeapon>().To<NullWeapon>();
And remove the if
from the Warrior.Attack()
method:
public void Attack()
{
this.weapon.Strike();
}
Of course, having a null IWeapon
is only useful if sometimes there is a concrete weapon, like Sword
or Spear
, and sometimes it's valid that there is just none. If there always needs to be a specific weapon, you don't need the null object pattern and you don't need a null object!
Having said that, let's extend the example a bit:
Let's say we have an application configuration for our game where we specify the era where the game takes place in. Let's say there's:
- pre-historical: humans didn't invent any weapons yet (well ok, there were no humans)
- middle-age:
Sword
it is! - future: there must be a
LaserGun
!
So let's create the bindings:
kernel.Bind<IWeapon>().To<NullWeapon>();
kernel.Bind<IWeapon>().To<Sword>().When(ctx => config.Era == MiddleAge);
kernel.Bind<IWeapon>().To<LaserGun>().When(ctx => config.Era == Future);
this gives us a "default" binding for NullWeapon
in case no .When(...)
matches, but when a .When(...)
matches, well, the matching binding is used!
Also see http://stackoverflow.com/questions/6546657/ninject-optional-injection for a real life example.
Tired of implementing all the null objects? You could opt to use a proxy instead, see the interception extension!
Sometimes - in very few occasions though - doing a null-check may be simpler then implementing a null object or even several null objects.
In these cases you may opt to use the IResolutionRoot.TryGet<T>() or IResolutionRoot.TryGetAndThrowOnInvalidBinding<T>()
extension methods.
Both return null
in case there is no matching binding (the usual rules apply like with constructor injection). However, there is a difference:
-
TryGet<T>
will try to instanciate the type in case there is a binding. If the instanciation fails withActivationException
, the exception is swallowed and null is returned. -
TryGetAndThrowOnInvalidBinding<T>
rethrows theActivationException
in case there is a matching binding.
Thus, TryGet<T>
may hide configuration issues.
Licensed under Apache 2 License
Contents
- Home
- Why Use Ninject
- Getting Started
- Dependency Injection By Hand
- Dependency Injection With Ninject
- Injection Patterns
- Multi Injection
- Object Scopes
- Modules and the Kernel
- Providers, Factory Methods and the Activation Context
- The Activation Process
- How Injection Works
- Contextual Binding
- Conventions-Based Binding