Published: 08 Oct 2010
By: Xianzhong Zhu

This article will serve as an elementary tutorial to help you quickly get started with the lightweight and open sourced dependency injector for .NET applications - Ninject.

Contents [hide]

Introduction

The main reason driving me to write this article is due to Balder - the famous 3D engine targeting Silverlight game. As you may image, in the infrastructure of the Balder engine, Ninject is widely used. In fact, in developing modern and large-scaled applications, especially underlining architectures, to create a loosely-coupled, highly-cohesive, and flexible ones, some dependency inject frameworks are usually required to come to help. Ninject is one of these; it mainly targets .NET based C# area.

So, this article will serve as an elementary tutorial to help you quickly get started with the lightweight and open sourced dependency injector for .NET applications - Ninject. And hence, we'll not touch anything upon Silverlight or ASP.NET MVC, but merely on the simplest Console samples.

NOTE

The development environments and tools we'll use in these series of articles are:

1. Windows XP Professional (SP3);

2. .NET 3.5 (currently Ninject 2.0 does not support .NET 4.0);

3. Visual Studio 2010;

4. Ninject 2.0 for .NET 3.5 (http://github.com/ninject/ninject/downloads);

What is Dependency Inject?

Design patterns, especially structural ones, often aim to resolve dependencies between objects, changing the dependence from upon the specific to on the abstract. In normal development, if we find clients rely on an object, we will usually abstract them to form abstract classes or interfaces, so that the clients can get rid of the dependence of the specific types.

In fact, something in the above process has been ignored - who bears the responsibility to choose the specific types related to the abstract ones required by the client? You will find, at most times, to create some types of models can more elegantly solve this problem. But another problem arises: what if your design is not the specific business logic, but the public library or framework of the program? At this time, you are a "service side," not that you call those structures, but they send you the abstract type. How to pass the client the processed abstract type is another matter.

Next, let's consider a simple example.

Suppose the client application needs an object that can provide the System.DateTime type. After that, whenever it is needed, it only needs to abstract the year part from it. So, the initial implementation may look like the following.

Listing 1: The initial solution

Later, for some reason, the developer finds .NET Framework comes with a date type precision that is not enough, and needs to provide other sources of TimeProvider, to ensure accuracy in the different functional modules with different TimeProviders. Thus, questions focus on the changes of the TimeProviders that will affect client applications; but, in fact, the clients only require using the abstract method to obtain the current time. To do this, we can add an abstract interface to ensure the clients depend only on the interface TimeProvider. Because this part of the client application needs only accurate to the year, so it can use a type called SystemTimeProvider. The new implementation is shown below.

Listing 2: The new implementation

This seems the client-side follow-up treatments all depend on the abstract SystemTimeProvider. And, the problem seems solved? No, it should also know the specific ITimeProvider. Therefore, you also need to add an object, which will choose a method of an instance of ITimeProvider passed to the client application; the object can be called Assembler. The new structure is shown in Figure 1 below.

Figure 1: The new dependency relationships after adding the Assembler object

The new dependency relationships after adding the 

Assembler object

In conclusion, the Assembler's responsibilities are as follows:

  • Know the type of each specific TimeProviderImpl.
  • According to the client's request, feed the abstract ITimeProvider back to the client.
  • Also responsible for the creation of TimeProviderImpl.

Below is one of the possible implementations of the Assemble.

Listing 3: One possible implementation of the Assemble

Up till this point, you should no doubt realize the important role of the Assembler above. In fact, as you may have doped out, Ninject just plays the role of the above Assembler and, of course, far more than that.

Next, let's turn to the really interesting topic.

Why Select Ninject?

There are already several dependency injection libraries available in .NET circles. Then, why select Ninject?

First, most of other frameworks heavily rely on XML configuration files to instruct the framework on resolve the related components of your application. This may bring numerous disadvantages.

Second, most of the other frameworks often require you to add several assembly dependencies to your project, which can possibly result in framework bloat. However, Ninject aims to keep things simple and straightforward.

Third, by introducing an extremely powerful and flexible form of binding -contextual binding, Ninject can be aware of the binding environment, and can change the implementation for a given service during activation.

Injection Patterns in Ninject

Ninject 2.0 supports three patterns for injection: constructor injection, property injection, and method injection. Next, let me introduce them by related examples one by one.

Constructor injection

The most frequently-used type should be constructor injection. When activating an instance of a type, Ninject will select one of the type's contructors by applying the following rules in order.

  • If a constructor has an [Inject] attribute, Ninject singles it out.
  • If no constructors have an [Inject] attribute, Ninject will select the one with the most parameters that Ninject understands how to resolve.
  • If no constructors are defined, Ninject will select the default parameterless constructor.

Next, let's take a related example.

Listing 4: Constructor injection in action

In the above code, the kernel plays the core role in the application. In most cases, we just need to create an instance of the class StandardKernel with zero or more instances of the module classes as the parameters. For this, please refer to the following definition of the constructor of the class StandardKernel.

Listing 5: The constructors of the StandardKernel class

Next, to request an instance of a type (mostly abstract, such as an interface or abstract class) from Ninject, we just need to call one of the related Get() extension methods. Here we used the following:

As for locating the corresponding concrete instance to be used inside the instance in the class Soldier, this is accomplished in the module WarriorModule through various kinds of Bind<T> ().To<T>() methods.

Please notice that only one constructor can be marked with an [Inject] attribute. Putting an [Inject] attribute on more than one constructor will result in Ninject throwing a NotSupportedException at the first time an instance of the type is requested.

Moreover, in Ninject 2.0 the [Inject] attribute can be left off completely. So, most of your code doesn't need to be aware of the existence of Ninject. And, therefore, you won't need to reference the Ninject related namespace/libraries, which is also most useful in the case that we cannot have access to the target source code but still require injecting dependencies into it.

Figure 2: The running-time snapshot related to the constructor injection demo

The running-time snapshot related to the constructor 

injection demo

Next, let's look at another kind of injection – property injection.

Property Injection

To make a property injected you must annotate it with [Inject]. Unlike the constructor injection rule, you can decorate multiple properties with an [Inject] attribute. However, the order of property injection is not deterministic, which means there is no way to tell in which order Ninject will inject their values in. Let's look at an example.

Listing 6: Property injection in action

In the above code, the property Weapon of the class Soldier is decorated with the annotation [Inject]. So, in the Main function (the client-side application) by defining the bold line, wherever the IWeapon interface appears it is identified as the concrete class Sword.

What's more, the module is not defined as the previous sample. In fact, in Ninject 2.0, the modules become optional – we can directly register bindings (still using the Bind<T> ().To<T>() methods) directly on the kernel.

Of course, the running-time result is easy to image. It is: Killed the foemen using Sword.

How about the method inject? It's also easy to understand.

Method Injection

The following gives the method injection related key code.

Listing 7: Method injection in action

As you've seen, the method Arm is annotated with [Inject] in the Soldier class. So later, by invoking kernel.Bind<IWeapon>().To<Gun>(); where the interface IWeapon (usually called a service type) is depended upon ( in the method Arm ), the concrete class Gun (also named an implementation type) is injected.

Also, above we've added the unbind test. This is more easily implemented by calling kernel.Unbind<IWeapon>();. After that, the service type IWeapon becomes another concrete class – Sword.

Now, let's, as usual, look at the running-time snapshot, as shown in Figure 3 below.

Figure 3: Method injection related demo’s output

Method injection related demo’s output

About Modules

With Ninject, the type bindings are typically collected into groups called modules, each of which represents an independent part of the application. To create modules, you just need to implement the INinjectModule interface. But, for simplicity, under most circumstances we should just extend the NinjectModule class. You can create as many modules as you'd like, and pass them all to the kernel's constructor. See the sample sketch below.

Listing 8: The multiple modules related sample sketch

That's it. Starting from the next section, let's continue to discuss some more advanced samples.

Bind to Provider

What is a provider? Why do we mention it? Virtually, as you've already seen, the type binding plays a significant role in Ninject. Rather than simply casting from one type to another, bindings are actually from a service type to a provider. A provider is an object that can create instances of a specified type, which is in some degree like a factory.

In Ninject 2.0, there are three built-in providers (defined in the namespace Ninject.Activation.Providers): CallbackProvider, ConstantProvider, and StandardProvider, the most important of which should be StandardProvider. In the previous example concerning the binding from IWeapon to Sword, we in fact declared a binding from IWeapon to StandardProvider. All of the other related stuffs happen behind the scenes; we at most time do not care about it. However, sometimes you might want to do some sort of complex custom initialization to your objects, rather than letting Ninject work its magic. In this case, you can create your own provider and bind directly to it. Let's create a related example, as shown in Listing 9 below.

Listing 9: Create a custom provider

Please notice the bold lines in the above code. First, we've defined a generic custom provider named SwordProvider<T>. In fact, the really important member is the CreateInstance method. However, for simplicity, we did nothing special but return the Value property. The second part of the bold lines is the most vital. As you no doubt have doped out, the functionality equals to the following (at least in this sample):

Anyway, the custom provider do provide us a free place to do some sort of complex custom initialization to our objects.

Factory Methods

A lighter weight alternative to writing IProvider implementations is to bind a service to a delegate method. Listing 10 gives a related sample.

Listing 10: A simplified method to implement a custom provider

The most interesting part should be the bold line. Here, I want to leave it to you to examine the details.

Finally, let's switch to the most important binding – contextual binding related topic.

Contextual Binding

One of the more powerful (and complex) features of Ninject is its contextual binding system. Up until this point, we've only been talking about default bindings - bindings that are used unconditionally, in any context.

In this section, we are going to look at the contextual binding. First of all, please check out the code below.

Listing 11: Several cases related to contextual binding

The above code gives three cases related to contextual binding. Please notice the bold lines inside the three modules. In the ToConstantServiceModule module, we achieve the target of binding the service (the interface ITester here) to the specified constant value. The second module SingletonServiceModule defines a typical Singleton case. Note, In Ninject 2.0, the annotation [Singleton] is no more supported; instead, it suggests using the following new syntax:

Here, the class IocTester is defined as a Singleton.

Finally, in the third module WithConstructorArgumentServiceModule, we defined a binding from ITester to IocTester, but required the argument (in this case logger) of the constructor of the class IocTester should be overridden with the specified value (an instance of the class FlatFileLogger here).

OK, there are still several more contextual binding defined in Ninject. With these bindings, you can bring Ninject's power and flexibility into full play. But the rest depond on you readers to continue digging.

About the Balder Engine and Ninject

As addressed previously, the motive drives me to write this article mainly results from the Balder engine. In my experience, to gain a thorough understanding with the engine, you have to make clear most of the details inside the Ninject framework. As you've known, the architecture design of the Balder engine depends heavily upon Ninject. In the previous samples, I've only show you the textbox styled routine to utilize Ninject, while in fact you can arm yourself with all you learn to reorganize your targets. Figure 4 indicates the class diagram for the PlatformKernel class in Balder, which greatly enhances and simplifies the role of StandKernal in Ninject.

Figure 4: The class diagram for the PlatformKernel class

The class diagram for the PlatformKernel class

Next, based upon the custom PlatformKernel class, Balder broke the above textbook-styled way to use Ninject 2.0 in its own way. Listing 12 below gives one of the most important binding definitions in the the PlatformKernel class.

Listing 12: Define required bindings in the PlatformKernel class constructor rather than in the suggested modules

As said above, in Ninject 2.0 we can now register bindings directly on the kernel; modules become optional. The above code also proves this, doesn't it?

Well, now you may also have understood why I mentioned the Balder engine - to master Ninject and to put it into your real development, this article is far from the real requirements – it just scratches the surface of it.

Summary

As pointed out at the beginning, this article mainly aims to serve as one of the most elementary tutorials concerning the Ninject framework. In fact, there are far more than those covered here. So, in your later application development, such as those related to WPF, Silverlight, ASP.NET MVC, etc. you should do more research into Ninject. And it's not until then can it become your own Swiss army knife in your work.

<<  Previous Article Continue reading and see our next or previous articles Next Article >>

About Xianzhong Zhu

I'm a college teacher and also a freelance developer and writer from WeiFang China, with more than fourteen years of experience in design, and development of various kinds of products and applications on Windows platform. My expertise is in Visual C++/Basic/C#, SQL Server 2000/2005/2008, PHP+MyS...

This author has published 81 articles on DotNetSlackers. View other articles or the complete profile here.

Other articles in this category


C# 4.0 Reflection Programming - Part 3
In the previous article, we used the reflection to obtain the information of an assembly, module, ty...
C# 4.0 Reflection Programming - Part 2
As introduced in the first article, the most typically-used tools associated with .NET reflection ar...
C# 4.0 Reflection Programming - Part 4
In this last article of this series, we will learn what to do with reflection. But before making the...
Understanding and Using Extension Methods
Extension methods were new to C# 3.0. They allow you to add a method to an existing type without hav...
Introduction to C# 3.0 features
C# 3.0 introduced some of very useful features built on top of 2.0. This article explains the usage,...

You might also be interested in the following related blog posts


Using T4 Templates for Simple DTOs read more
What is Softwaremaker doing now ? read more
Dynamic in C# 4.0: Creating Wrappers with DynamicObject read more
I finally got my Behringer Podcastudio working read more
Debunking the duct tape programmer read more
Building a class browser with Microsoft Ajax 4.0 Preview 5 read more
In Response To DNR 476 read more
Demeter Transmogrifiers To The Rescue read more
How to use Ninject 2 with ASP.NET MVC read more
Thoughts on agile design and platforms read more
Top
 
 
 

Please login to rate or to leave a comment.