Comparing .Net IoC containers, part three: Ninject
I have used NInject before and found it easy to work with. We used it because at the time it was the only IoC container that worked with Silverlight. The syntax is quite different to the others - you define the configuration in one or more Modules which inherit from NinjectModule, and are then loaded into the kernel.
In practice, the modules aren't such an overhead. You may end up with just one "live" module for use in production, and one or more modules with test configs. There's nothing to stop these modules being constructed with parameters and flags to register different things. You can construct a container with multiple modules, so you can mix and match them for different areas of your application.
A basic usage of the Ninject container is like this:
IKernel kernel = new StandardKernel(new VanillaModule()); SweetShop sweetShop = kernel.Get<SweetShop>();
Of course the type binding are hidden inside the module. Nevertheless, NInject is quite easy to use. The bindings inside the module uses syntax that is fluent, but manages to be compact, readable and discoverable. On the whole I find it one of the easiest syntaxes to read and use among IoC containers.
A module with basic mapping is done like this:
public class VanillaModule : NinjectModule
{
public override void Load()
{
Bind<IJellybeanDispenser>().To<VanillaJellybeanDispenser>();
}
}
But there is a second syntax that Ian Davis has pointed out. You don't need modules. The kernel has a Bind<>() methods alowing you to register against it directly, e.g.
IKernel kernel = new StandardKernel(); kernel.Bind<IJellybeanDispenser>().To<VanillaJellybeanDispenser>(); SweetShop sweetShop = kernel.Get<SweetShop>();
There's also a kernel.Unbind<>() method to change registrations.
Like Unity, types with discoverable constructors do not need to be registered.
Objects are transient by default. A singleton is made with
Bind<IJellybeanDispenser>() .To<VanillaJellybeanDispenser>() .InSingletonScope();
A constructor argument can be set with
Bind<IJellybeanDispenser>()
.To<AnyJellybeanDispenser>()
.WithConstructorArgument("jellybean", Jellybean.Lemon);
A pre-made instance with
Bind<IJellybeanDispenser>() .ToConstant(new AnyJellybeanDispenser(Jellybean.Cocoa));
And a factory method can be registered with
Bind<IJellybeanDispenser>() .ToMethod(c => new AnyJellybeanDispenser(Jellybean.Orange));
Ninject: The Code
The code is maintained in a repository on Github. Here's the complete code for the tests:
namespace IoCComparison
{
using System.Collections.Generic;
using System.Linq;
using Ninject;
using Ninject.Modules;
using NUnit.Framework;
/// <summary>
/// Ninject, like Unity, only requires registrations from types were there is a mapping
/// But the mappings are fixed and inside modules
/// </summary>
public class VanillaModule : NinjectModule
{
public override void Load()
{
Bind<IJellybeanDispenser>().To<VanillaJellybeanDispenser>();
}
}
public class StrawberryModule : NinjectModule
{
public override void Load()
{
Bind<IJellybeanDispenser>().To<StrawberryJellybeanDispenser>();
}
}
public class AniseedModule : NinjectModule
{
public override void Load()
{
Bind<SweetShop>().To<AniseedSweetShop>();
}
}
public class SingletonServiceModule : NinjectModule
{
public override void Load()
{
Bind<IJellybeanDispenser>().To<VanillaJellybeanDispenser>().InSingletonScope();
}
}
public class LemonModule : NinjectModule
{
public override void Load()
{
Bind<IJellybeanDispenser>().To<AnyJellybeanDispenser>()
.WithConstructorArgument("jellybean", Jellybean.Lemon);
}
}
public class CocoaModule : NinjectModule
{
public override void Load()
{
Bind<IJellybeanDispenser>().ToConstant(new AnyJellybeanDispenser(Jellybean.Cocoa));
}
}
public class OrangeModule : NinjectModule
{
public override void Load()
{
Bind<IJellybeanDispenser>().ToMethod(c => new AnyJellybeanDispenser(Jellybean.Orange));
}
}
public class MultipleDispenserModule : NinjectModule
{
public override void Load()
{
Bind<IJellybeanDispenser>().To<VanillaJellybeanDispenser>();
Bind<IJellybeanDispenser>().To<StrawberryJellybeanDispenser>();
}
}
[TestFixture]
public class NInjectTestClass
{
[Test]
public void CanMakeSweetShopWithVanillaJellybeans()
{
IKernel kernel = new StandardKernel(new VanillaModule());
SweetShop sweetShop = kernel.Get<SweetShop>();
Assert.AreEqual(Jellybean.Vanilla, sweetShop.DispenseJellyBean());
}
[Test]
public void CanMakeSweetShopWithVanillaJellybeansWithoutModules()
{
IKernel kernel = new StandardKernel();
kernel.Bind<IJellybeanDispenser>().To<VanillaJellybeanDispenser>();
SweetShop sweetShop = kernel.Get<SweetShop>();
Assert.AreEqual(Jellybean.Vanilla, sweetShop.DispenseJellyBean());
}
[Test]
public void CanMakeSweetShopWithStrawberryJellybeans()
{
IKernel kernel = new StandardKernel(new StrawberryModule());
SweetShop sweetShop = kernel.Get<SweetShop>();
Assert.AreEqual(Jellybean.Strawberry, sweetShop.DispenseJellyBean());
}
[Test]
public void JellybeanDispenserHasNewInstanceEachTime()
{
IKernel kernel = new StandardKernel(new VanillaModule());
SweetShop sweetShop = kernel.Get<SweetShop>();
SweetShop sweetShop2 = kernel.Get<SweetShop>();
Assert.IsFalse(ReferenceEquals(sweetShop, sweetShop2), "Root objects are equal");
Assert.IsFalse(ReferenceEquals(sweetShop.SweetVendingMachine, sweetShop2.SweetVendingMachine), "Contained objects are equal");
Assert.IsFalse(ReferenceEquals(sweetShop.SweetVendingMachine.JellybeanDispenser, sweetShop2.SweetVendingMachine.JellybeanDispenser), "services are equal");
}
[Test]
public void CanMakeSingletonJellybeanDispenser()
{
IKernel kernel = new StandardKernel(new SingletonServiceModule());
SweetShop sweetShop = kernel.Get<SweetShop>();
SweetShop sweetShop2 = kernel.Get<SweetShop>();
Assert.IsFalse(ReferenceEquals(sweetShop, sweetShop2), "Root objects are equal");
Assert.IsFalse(ReferenceEquals(sweetShop.SweetVendingMachine, sweetShop2.SweetVendingMachine), "Contained objects are equal");
// should be same service
Assert.IsTrue(ReferenceEquals(sweetShop.SweetVendingMachine.JellybeanDispenser, sweetShop2.SweetVendingMachine.JellybeanDispenser), "services are not equal");
}
[Test]
public void CanMakeAniseedRootObject()
{
IKernel kernel = new StandardKernel(new AniseedModule());
SweetShop sweetShop = kernel.Get<SweetShop>();
Assert.AreEqual(Jellybean.Aniseed, sweetShop.DispenseJellyBean());
}
[Test]
public void CanUseAnyJellybeanDispenser()
{
IKernel kernel = new StandardKernel(new LemonModule());
SweetShop sweetShop = kernel.Get<SweetShop>();
Assert.AreEqual(Jellybean.Lemon, sweetShop.DispenseJellyBean());
}
[Test]
public void CanUseConstructedObject()
{
IKernel kernel = new StandardKernel(new CocoaModule());
SweetShop sweetShop = kernel.Get<SweetShop>();
Assert.AreEqual(Jellybean.Cocoa, sweetShop.DispenseJellyBean());
}
[Test]
public void CanUseObjectFactory()
{
IKernel kernel = new StandardKernel(new OrangeModule());
SweetShop sweetShop = kernel.Get<SweetShop>();
Assert.AreEqual(Jellybean.Orange, sweetShop.DispenseJellyBean());
}
[Test]
public void CanRegisterMultipleDispensers()
{
IKernel kernel = new StandardKernel(new MultipleDispenserModule());
IEnumerable<IJellybeanDispenser> dispensers = kernel.GetAll<IJellybeanDispenser>();
Assert.IsNotNull(dispensers);
Assert.AreEqual(2, dispensers.Count());
}
}
}
2 Comments
anon said
The sample code is a sweet intro for someone who's just getting into the whole DI / IoC thing with Ninject. Thanks you! (Pun intended.)
bbqchickenrobot said
Yes, great tutorial for a beginner! Been reading for six months now and most of DI/IoC made sense, but with Ninject there wasn't much documentation on how to do it. Also, you may want to add a second post on the Ninject providers which will allow you to instantiate an object + configure as necessary. Let's say, for example, an OR/M ... or repository, etc....