Introduction
I will assume that you have a basic understanding of Decoupled Architecture and Dependency injection in application development.
We know that DI or IoC is related to Decoupled Architecture that might provide better control and an easy way to maintain software applications. Fine, and probably you may know that there are three ways we can inject dependency using Dependency injection technique. Like
- Constructor Injection
- Setter getter/property injection
- Method injection
So, those are the possible solutions to implement Dependency injection. Now, let's get to the next concept. Is there any way to handle those types of injection in a better and smooth way? Yes, we can use an IoC container.
If IoC container is a new term to you then here is a single-line introduction to it. Though this is not a dedicated article for IoC containers, I will suggest you to refer to any good article to understand IoC container fully.
IoC container is nothing but a repository of dependent objects, we can collect all dependent objects in a single place, so that in a single go we can resolve dependencies of some objects that are dependent on those objects. Generally an IoC container are a piece of clever program that detects dependencies automatically (yes, almost magically, at least I think) .The truth to be told, if you want you can implement your own IoC container too. It's not rocket science , but people choose the best and most popular one to reduce time and get the full benefit of the IoC container.
There are many IoC containers available in the market, the choice is yours but for this article we will use Ninject, which is smart, flexible and one of the popular IoC containers.
The purpose of the article is to implement three types of DI using Niject. If you are very new to Niject then I suggest you go through my first article to learn the Ninject installation process in Visual Studio from the NuGet Package Manager.
Here is a sample implementation of the banking process. Let's discuss the example and try to understand it. In this example all classes implemented their corresponding Interface. In the BankingTransaction class we have implemented a Withdraw() function to do the withdraw operation and after completion it will Log the process and it will notify too the account holder. Have a look at the following example.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject;
using Ninject.Modules;
namespace Client
{
public interface ILogger
{
void Log();
}
public class EventLog : ILogger
{
public void Log()
{
Console.WriteLine("Event Log successful");
}
}
public interface INotifier
{
void SendNotification();
}
public class SMSNotification : INotifier
{
public void SendNotification()
{
Console.WriteLine("Notification sent by SMS");
}
}
public interface IBankingOperation
{
void Withdraw();
}
public class BankingOperation : IBankingOperation
{
public void Withdraw()
{
Console.WriteLine("Money Withdrawal successful");
}
}
public class BankTran
{
public IBankingOperation ObjBankingOperation = null;
public INotifier ObjNotifier = null;
public ILogger ObjLogger = null;
[Inject]
public BankTran(IBankingOperation tmpBO)
{
ObjBankingOperation = tmpBO;
}
[Inject]
public INotifier Notifier { set { ObjNotifier = value; } }
[Inject]
public void LogTransaction(ILogger tmpLog)
{
ObjLogger = tmpLog;
}
public void MakeTransaction()
{
ObjBankingOperation.Withdraw();
ObjLogger.Log();
ObjNotifier.SendNotification();
Console.WriteLine("Transaction Successful");
}
}
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel();
kernel.Bind<ILogger>().To<EventLog>();
kernel.Bind<INotifier>().To<SMSNotification>();
kernel.Bind<IBankingOperation>().To<BankingOperation>();
BankTran tran = kernel.Get<BankTran>();
tran.MakeTransaction();
Console.ReadLine();
}
}
}
Let's discuss the BankTran class that is the most important one for this article. This class is dependent on their other classes and they are EventLog, SMSNotification and BankingOperation. Since the object of the BankTran object is dependent on the object of those three classes, we must pass them at the time of Object creation of the BankTran class.
Now, here the IoC container comes into play. To resolve this dependency, we have kept all dependent classes in a container (in the Main() function) and resolving all Dependency in one go in this line.
BankTran tran = kernel.Get<BankTran>();
Now, the “tran” object can execute all the functions of its parent class. If you run the application, you will find that all functions have executed.
Now, the question might come in your mind, if I do not use IoC then how do I solve the problem. The point is, it's not mandatory to use an IoC container to resolve dependency, we can use the traditional way too. Have a look at the following implementation.
using System;
using System.Collections.Generic;
using System.Text;
namespace Client
{
public interface ILogger
{
void Log();
}
public class EventLog : ILogger
{
public void Log()
{
Console.WriteLine("Event Log successful");
}
}
public interface INotifier
{
void SendNotification();
}
public class SMSNotification : INotifier
{
public void SendNotification()
{
Console.WriteLine("Notification sent by SMS");
}
}
public interface IBankingOperation
{
void Withdrawl();
}
public class BankingOperation : IBankingOperation
{
public void Withdrawl()
{
Console.WriteLine("Money Withdrawal successful");
}
}
public class BankTran
{
public IBankingOperation ObjBankingOperation = null;
public INotifier ObjNotifier = null;
public ILogger ObjLogger = null;
public BankTran(IBankingOperation tmpBO)
{
ObjBankingOperation = tmpBO;
}
public INotifier Notifier { set { ObjNotifier = value; } }
public void LogTransaction(ILogger tmpLog)
{
ObjLogger = tmpLog;
}
public void MakeTransaction()
{
ObjBankingOperation.Withdrawl();
ObjLogger.Log();
ObjNotifier.SendNotification();
Console.WriteLine("Transaction Successful");
}
}
class Program
{
static void Main(string[] args)
{
// Creating object by passing an object of BankingOperation class (constructor injection)
BankTran tran = new BankTran(new BankingOperation());
// Set property using an object of SMSNotification class (property injection)
tran.Notifier = new SMSNotification();
// Set object on EventLog class through function (method injection)
tran.LogTransaction(new EventLog());
tran.MakeTransaction();
Console.ReadLine();
}
}
}
Now, compare the implementation of both Main() functions. In this approach we are specifying a dependent object explicitly depending on its injection type but in the case of IoC container we did not.
There is another advantage of IoC container; once we create a container, we can use it for many (or N) number of objects to resolve it's dependency where this facility is not available in the traditional way.
Conclusion
This is the advantage of IoC containers; to resolve dependency of objects. I hope you have understood the concept and want to give Ninject a try.