Dependency injection with Ninject

I am figuring out how the Ninject dependency injection container (or short DI-container) works. I assume you already know how to create a console application in Visual Studio or Visual Studio Express. When you don't know how to do this, first visit this page on my site and watch the video.

Download Ninject first for the .Net Framework you're using. I used the .Net Framework 4.0 version. When you're not sure you can see the .Net Framework version you're using in the properties of your project in Visual Studio (right-click on your project in the Solution Explorer and choose properties and look for Target framework). The download is a zip file. Unzip it in a certain directory on your computer, e.g.: C:\Projects\Visual Studio 2010\NinjectDotNet40.

The most important part of this zip file is the Ninject.dll file. After you've created a new Console Application project in Visual Studio, you'll have to add Ninject as a reference to your project. You can do this by right-clicking on your References folder in your Solution Explorer in Visual Studio and click 'Add Reference'. Next, in the dialog click on the 'Browse' tabpage and go to the folder in which you installed Ninject and search for the Ninject.dll file. Click on it and close the dialog by clicking 'Ok'. When you succeeded, Ninject is displayed in your Solution Explorer.

Now you're ready to learn what dependency injection is all about. The advantage of DI is that your code becomes less coupled and this results in making it easier to test (unit testing). This is because then several parts of your code can be tested in isolation. I've learned from several people that you should (if you can) unit test your code. Unit testing is a separate topic, that I want to discuss on my site later on.

There's quite some discussion going on as to whether DI is good or bad, but I think DI is a good thing because it makes TDD (test driven development) easier and I think loosly coupled code is better than tightly coupled code. It makes it easier to exchange one piece of code with another. I like interfaces. Maybe you need to understand how interfaces work, but I think when you read this article it's not that hard to understand what is happening.

Bye the way, later on I learned that Ninject is among the slowest performing DI-containers (or inversion of control containers) as you can read here. However, I learned a lot by figuring out what you can do with it.

Step 1 - A simple Sword and Samurai class

I just followed the tutorial on the Ninject site and made first a simple console program in C#. In this first step you'll see how without DI commonly a program is being made. There are two classes: Sword and Samurai. The Samurai class makes use of the Sword class and an instance of this Sword class is created inside the constructor of the Samurai class. This works well, however the Samurai class is directly dependant upon the Sword class.

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace NinjectTutor { class Sword { public void Hit(string target) { Console.WriteLine("Chopped {0} clean in half", target); } } class Samurai { readonly Sword sword; public Samurai() { this.sword = new Sword(); } public void Attack(string target) { this.sword.Hit(target); } } class Program { static void Main(string[] args) { var warrior = new Samurai(); warrior.Attack("the evildoers"); Console.ReadLine(); } } }

Step 2 - Adding an interface

Next, I added the IWeapon interface and derived the Sword class from this interface. Also the Samurai class now has a property weapon of type IWeapon. Inside of the constructor of the Samurai class still use is being made of the Sword class, but when you create other classes that derive from IWeapon, it becomes easier to change the type of weapon the Samurai class uses. In this step the Samurai class therefore is still dependant upon the Sword class.

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace NinjectTutor { interface IWeapon { void Hit(string target); } class Sword : IWeapon { public void Hit(string target) { Console.WriteLine("Chopped {0} clean in half", target); } } class Samurai { readonly IWeapon weapon; public Samurai() { this.weapon = new Sword(); } public void Attack(string target) { this.weapon.Hit(target); } } class Program { static void Main(string[] args) { var warrior = new Samurai(); warrior.Attack("the evildoers"); Console.ReadLine(); } } }

Step 3 - Inject Sword as parameter in the Samurai constructor

To further decouple the Samurai class from the Sword class, now the kind of weapon is being injected via the constructor that has an IWeapon as parameter. You'll see that in this step the Samurai class is no longer dependant upon the Sword class. In the Main method the Sword is passed as the kind of weapon the Samurai class uses.

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace NinjectTutor { interface IWeapon { void Hit(string target); } class Sword : IWeapon { public void Hit(string target) { Console.WriteLine("Chopped {0} clean in half", target); } } class Samurai { readonly IWeapon weapon; public Samurai(IWeapon weapon) { this.weapon = weapon; } public void Attack(string target) { this.weapon.Hit(target); } } class Program { static void Main(string[] args) { var warrior = new Samurai(new Sword()); warrior.Attack("the evildoers"); Console.ReadLine(); } } }

Step 4 - Add a new weapon and create two warriors with different weapons

In this step a new weapon class Shuriken is defined, which also derives from the IWeapon interface. Therefore it must also implement the Hit method. In the main method two warriors are created, warrior1 and warrior2 and they use a different weapon. Warrior1 makes use of a Shuriken and warrior2 makes use of a Sword, resulting in different response when the attack method is called.

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace NinjectTutor { interface IWeapon { void Hit(string target); } class Sword : IWeapon { public void Hit(string target) { Console.WriteLine("Chopped {0} clean in half", target); } } class Shuriken : IWeapon { public void Hit(string target) { Console.WriteLine("Pierced {0}'s armor", target); } } class Samurai { readonly IWeapon weapon; public Samurai(IWeapon weapon) { this.weapon = weapon; } public void Attack(string target) { this.weapon.Hit(target); } } class Program { static void Main(string[] args) { var warrior1 = new Samurai(new Shuriken()); var warrior2 = new Samurai(new Sword()); warrior1.Attack("the evildoers"); warrior2.Attack("the evildoers"); Console.ReadLine(); } } }

When you run this program, it will output the following lines:
Pierced the evildoers's armor
Chopped the evildoers clean in half

Step 5 - Making use of Ninject to manage the dependencies

In this step, finally Ninject is being used to manage dependencies. Note that new using statements are added on top of the sourcecode that makes sure that your sourcecode is using Ninject. The actual work of wiring thing together is being done by Ninject in the Main method. This is the entry-point of the application. Mark Seemann writes in his book Dependency Injection in .NET that you should only make use of your DI-container in what he calls the COMPOSITION ROOT. This should be a unique location in the application where the modules are composed together. This location should be as close as possible to the application entry-point, which is in our case the Main method.

You need to create a kernal instance first. Next you tell Ninject to Bind the IWeapon to the Sword class. Whenever Ninject needs an implementation for the IWeapon, it will make use of the Sword class.

The warrior is created in the Main method by getting the Samurai class from the Ninject kernel. Because the Samurai class needs an IWeapon in the constructor as a parameter, Ninject needs to know which implementation for this IWeapon to use. We already did this in the call to the Bind method. So Ninject wires up an Samurai class with a Sword class being injected in the Samurai constructor.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; using Ninject.Modules; namespace NinjectTutor { interface IWeapon { void Hit(string target); } class Sword : IWeapon { public void Hit(string target) { Console.WriteLine("Chopped {0} clean in half", target); } } class Samurai { readonly IWeapon weapon; [Inject] // This attribute is optional public Samurai(IWeapon weapon) { if (weapon == null) throw new ArgumentNullException("weapon"); this.weapon = weapon; } public void Attack(string target) { this.weapon.Hit(target); } } class Program { static void Main(string[] args) { Ninject.IKernel kernel = new StandardKernel(); kernel.Bind<IWeapon>().To<Sword>(); var warrior = kernel.Get<Samurai>(); warrior.Attack("the evildoers"); Console.ReadLine(); } } }

Step 6 - Making use of NinjectModules

In this step you'll see how you can make use of a class that derives from NinjectModule. Inside this class you'll override the Load method, in which you'll arrange the bindings. In the Main method this class is being used when creating the kernel instance.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; using Ninject.Modules; namespace NinjectTutor { interface IWeapon { void Hit(string target); } class Sword : IWeapon { public void Hit(string target) { Console.WriteLine("Chopped {0} clean in half", target); } } class Samurai { readonly IWeapon weapon; [Inject] // This attribute is optional public Samurai(IWeapon weapon) { if (weapon == null) throw new ArgumentNullException("weapon"); this.weapon = weapon; } public void Attack(string target) { this.weapon.Hit(target); } } public class WarriorModule : NinjectModule { public override void Load() { Bind<IWeapon>().To<Sword>(); Bind<Samurai>().ToSelf().InSingletonScope(); } } class Program { static void Main(string[] args) { Ninject.IKernel kernel = new StandardKernel(new WarriorModule()); Samurai warrior = kernel.Get<Samurai>(); warrior.Attack("the evildoers"); Console.ReadLine(); } } }

 

 

Dependency injection with Ninject