Get Started with Ninject in C# Programming

in #utopian-io6 years ago (edited)

What Will I Learn?

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.

Requirements

  1. Ninject
  2. .NET;
  3. Visual Studio;
  4. Windows

Difficulty

Intermediate

Get Started with Ninject in C# Programming

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. 

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 ;

2. .NET;

3. Visual Studio;

4. Ninject  

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.

1. The initial solution 

class TimeProvider
{
   public DateTime CurrentDate { get { return DateTime.Now; } }
}
public class Client
{
   public int GetYear()
   {
       TimeProvider timeProvier = new TimeProvider();
       return timeProvier.CurrentDate.Year;
   }
}

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.

2. The new implementation
 

interface ITimeProvider
{
   DateTime CurrentDate { get;}
}
class SystemTimeProvider : ITimeProvider
{
   public DateTime CurrentDate { get { return DateTime.Now; } }
}
public class Client
{
   public int GetYear()
   {
       ITimeProvider timeProvier = new SystemTimeProvider();
       return timeProvier.CurrentDate.Year;
   }
}

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

 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.

3. One possible implementation of the Assemble 

public class Assembler
{
   private static Dictionary<type, type> dictionary = new Dictionary<type, type>();
   static Assembler()
   {
       dictionary.Add(typeof(ITimeProvider), typeof(SystemTimeProvider));
   }
   public object Create(Type type)
   {
       if ((type == null) || !dictionary.ContainsKey(type)) throw new NullReferenceException();
       Type targetType = dictionary[type];
       return Activator.CreateInstance(targetType);
   }
   public T Create<t>()
   {
       return (T)Create(typeof(T));
   }
}

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 supports three patterns for injection: constructor injection, property injection, and method injection. Next, let me introduce them by related examples one by one.

4. Constructor injection in action 

//...other namespaces reference omitted
using Ninject;//InjectProperty
using Ninject.Modules;//NinjectModule
namespace ConstructorInjectionDemoNS
{
   interface IWeapon
   {
       void Hit(string target);
   }
   class Sword : IWeapon
   {
       public void Hit(string target)
       {
           Console.WriteLine("Killed {0} using Sword", target);
       }
   }
   class Gun : IWeapon
   {
       public void Hit(string target)
       {
           Console.WriteLine("Killed {0} using Gun", target);
       }
   }
   class Soldier
   {
       private IWeapon _weapon;
       [Inject]
       public Soldier(IWeapon weapon)
       {
           _weapon = weapon;
       }
       public void Attack(string target)
       {
           _weapon.Hit(target);
       }
   }
   class WarriorModule : NinjectModule
   {
       public override void Load()
       {
           Bind<iweapon>().To<sword>();
           Bind<soldier>().ToSelf();
       }
   }
   class Program
   {
       static void Main(string[] args)
       {
           IKernel kernel = new StandardKernel(new WarriorModule());
           Soldier warrior = kernel.Get<soldier>();
           warrior.Attack("the foemen");
           Console.ReadKey();
       }
   }
}

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.

5. The constructors of the StandardKernel class
 

public class StandardKernel : KernelBase
{
public StandardKernel(params INinjectModule[] modules) : base(modules) { }
public StandardKernel(INinjectSettings settings, params INinjectModule[] modules) : base(settings,

modules) { }
//... others omitted
}

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:

Soldier warrior = kernel.Get<soldier>();

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.

lease 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 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

 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.

6. Property injection in action 

namespace PropertyInjectionDemoNamespace
{
// the definitions of interface IWeapon, class Sword and
//Gun are all the same as above
//... (omitted for brevity)
   class Soldier
   {
       [Inject]
       public IWeapon Weapon { get; set; }
       public void Attack(string target)
       {
           Weapon.Hit(target);
       }
   }
   class PropertyInjectionDemo
   {
       static void Main(string[] args)
       {
           IKernel kernel = new StandardKernel();
           kernel.Bind<iweapon>().To<sword>();
           Soldier warrior = kernel.Get<soldier>();
           warrior.Attack("the foemen");
           Console.ReadKey();
       }
   }
}

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, 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.

7. Method injection in action 

namespace MethodInjectionDemoNS
{
// the definitions of interface IWeapon, class Sword and
//Gun are all the same as above
//... (omitted for brevity)
   class Soldier {
     private IWeapon _weapon;
     [Inject]
     public void Arm(IWeapon weapon) {
       _weapon = weapon;
     }
     public void Attack(string target) {
       _weapon.Hit(target);
     }
   }
   class MethodInjectionDemo
   {
       static void Main(string[] args)
       {
           IKernel kernel = new StandardKernel();
           kernel.Bind<iweapon>().To<gun>();
           Soldier warrior = kernel.Get<soldier>();
           warrior.Attack("the foemen");
           //unbind test
           kernel.Unbind<iweapon>();
           kernel.Bind<iweapon>().To<sword>();
           Soldier warrior2 = kernel.Get<soldier>();
           warrior2.Attack("the foemen");
           Console.ReadKey();
       }
   }
}

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

 

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.

8. The multiple modules related sample sketch 

class Module1 {
 public void Load() { ... }
}
class Module2 {
 public void Load() { ... }
}
class Program {
 public static void Main() {
   IKernel kernel = new StandardKernel(new Module1(), new Module2(), ...);
   ...
 }
}

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, 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. Let's create a related example, as shown in Listing 9 below.

9. Create a custom provider
 

//... (others left out)
using Ninject;//InjectProperty
using Ninject.Modules;//NinjectModule
using Ninject.Activation;//IProvider,Provider
namespace BindToProviderNameSpace
{
// the definitions of interface IWeapon, class Sword and
//Gun are all the same as above
//... (omitted for brevity)
   class Soldier
   {
       [Inject]
       public IWeapon Weapon { get; set; }
       public void Attack(string target)
       {
           Weapon.Hit(target);
       }
   }
   public class SwordProvider<t> : Provider<t>
   {
       public T Value { get; private set; }
       public SwordProvider(T value)
       {
           Value = value;
       }
       protected override T CreateInstance(IContext context)
       {
           // do some sort of complex custom initialization
           return Value;
       }
   }
   class BindToProviderDemo
   {
       static void Main(string[] args)
       {
           IKernel kernel = new StandardKernel();
           kernel.Bind<iweapon>().ToProvider(new SwordProvider<sword>(new Sword()));
           Soldier warrior = kernel.Get<soldier>();
           warrior.Attack("the foemen");
           Console.ReadKey();
       }
   }
}

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): 

kernel.Bind<iweapon>

().To<sword>();

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.

10. A simplified method to implement a custom provider 

namespace FactoryMethodNameSpace
{
// the definitions of interface IWeapon, class Sword and
//Gun are all the same as above
//... (omitted for brevity)
   class Soldier
   {
       [Inject]
       public IWeapon Weapon { get; set; }
       public void Attack(string target)
       {
           Weapon.Hit(target);
       }
   }
   class FactoryMethodDemo
   {
       static void Main(string[] args)
       {
           IKernel kernel = new StandardKernel();
           kernel.Bind<iweapon>().ToMethod(context => new Sword());
           Soldier warrior = kernel.Get<soldier>();
           warrior.Attack("the foemen");
           Console.ReadKey();
       }
   }
}

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.

11. Several cases related to contextual binding 

namespace ContextualBindingDemoNS
{
   public interface ILogger
   {
       void Write(string message);
   }
   public class FlatFileLogger : ILogger
   {
       public void Write(string message)
       {
           Console.WriteLine(String.Format("Message:{0}", message));
           Console.WriteLine("Target:FlatFile");
       }
   }
   public class DatabaseLogger : ILogger
   {
       public void Write(string message)
       {
           Console.WriteLine(String.Format("Message:{0}", message));
           Console.WriteLine("Target:Database");
       }
   }
   interface ITester
   {
       void Test();
   }
   class IocTester : ITester
   {
       private ILogger _logger;
       public IocTester() { }//This parameterless constructor is required by 'Activator.CreateInstance'
       public IocTester(ILogger logger)
       {
           _logger = logger;
       }
       public void Test()
       {
           _logger.Write("John Says: Hello Ninject!");
       }
   }
   public class ToConstantServiceModule : Ninject.Modules.NinjectModule
   {
       private readonly Type testerType;
       public ToConstantServiceModule(Type TesterType)
       {
           testerType = TesterType;
       }
       public override void Load()
       {
           Bind<ilogger>().To<flatfilelogger>();
           var testertype = Activator.CreateInstance(testerType) as ITester;
           Bind<itester>().ToConstant(testertype);
       }
   }
   public class SingletonServiceModule : NinjectModule
   {
       public override void Load()
       {
           Bind<ioctester>().ToSelf().InSingletonScope();
       }
   }
   public class WithConstructorArgumentServiceModule : NinjectModule
   {
       public override void Load()
       {
           Bind<ilogger>().To<flatfilelogger>();
           Bind<itester>().To<ioctester>().WithConstructorArgument("logger", new FlatFileLogger());
       }
   }
   class ContextualBindingDemo
   {
       static void Main(string[] args)
       {
           IKernel kernel = new StandardKernel(new WithConstructorArgumentServiceModule());
           IocTester tester = kernel.Get<ioctester>();
           tester.Test();
           Console.WriteLine("continues..");
           Console.Read();
       }
   }
}

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, the annotation [Singleton] is no more supported; instead, it suggests using the following new syntax:

Bind<ioctester>().ToSelf().InSingletonScope();

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

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

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

public class PlatformKernel : AutoKernel
{
public PlatformKernel(Type platformType)
{
var platform = Activator.CreateInstance(platformType) as IPlatform;
Bind<iplatform>().ToConstant(platform);
Bind<idisplaydevice>().ToConstant(platform.DisplayDevice);
Bind<imousedevice>().ToConstant(platform.MouseDevice);
Bind<ifileloader>().To(platform.FileLoaderType).InSingletonScope();
Bind<igeometrycontext>().To(platform.GeometryContextType);
Bind<ispritecontext>().To(platform.SpriteContextType);
Bind<iimagecontext>().To(platform.ImageContextType);
Bind<ishapecontext>().To(platform.ShapeContextType);
Bind<imaterialcalculator>().To(platform.MaterialCalculatorType);
Bind<iskyboxcontext>().To(platform.SkyboxContextType);
}
//......(others elided to save space)

As said above, in Ninject 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. 



Posted on Utopian.io - Rewarding Open Source Contributors

Sort:  

Thank you for the contribution. It has been approved.

You can contact us on Discord.
[utopian-moderator]

Hey @rufu I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • Seems like you contribute quite often. AMAZING!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x

Coin Marketplace

STEEM 0.28
TRX 0.11
JST 0.031
BTC 68734.33
ETH 3745.98
USDT 1.00
SBD 3.72