What is Dependency Injection and What Are Its Types? How to implement DI?

Introduction
 
IOC and DI helps us to get rid of dependency from your code. Why should I use dependency injection?
 
Let’s say we have a Smartphone class that contains various objects, such as Processor, Ram, OS, Storage, etc. In this situation, the Smartphone class is responsible for creating all dependency objects. Now, what if we decide to get rid of Snapdragon Processor in the future, and rather want to use a MediaTek Processor? We will need to recreate the Smartphone object with a new MediaTek dependency. However, when using dependency injection (DI), we can change the Processor at runtime because dependencies can be injected at runtime rather than at the compile time. We can think of DI as the intermediary in our code who does all the work of creating the preferred Processor object and providing it to the Smartphone class. It makes our Smartphone class independent from creating the objects of as Processor, Ram, OS, Storage, etc.
 
Let’s first understand the basic terminology.
 
How do we achieve loosely coupled classes? With tightly coupled classes, implementation of inversion of control, DIP using abstraction, and implementation of DI using an IOC Container (UnityContainer)
 
What do we need to take care of?
 
There are 3 entities involved.
  • The Dependent class is a class which depends on the dependency class
  • The Dependency class is a class that provides service/data to the dependent class.
  • The interface injects the Dependency class object into the dependent class.
For our examples we will have:
  • The Dependent class: ProductDataAccess class: Waiting for business logic to apply on data provided by entity class. 
  • The Dependency class: ProductDetails class: Entity of Product table, which holds data.
  • The Injector interface: IProductDetails interface
There are 3 types of Dependency Injection.
  • Constructor Injection
  • Property Injection
  • Method Injection
Let's create a project in Visual Studio and follow a proper structure. We will create 3 layered architecture:
 
(Presentation -> BusinessLogic -> DataAccess)
 
Add Console application Project named Presentation (This is our entry point into the project). Then add one more DLL named DataAccess. (For now, we’re not going to create database, as our main focus in this blog is to understand DI). We will create an Entity class with default values as data. Add one more DLL named BusinessLogic. As per the 3 layered architectural pattern, UI communicates with BusinessLogic and BusinessLogic gets data from DataAccess layer. To achieve this, first add BusinessLogic’s reference in Presentation module, then add DataAccess’ reference in BusinessLogic.
 
Refer to the below image for clarification
 
 
Once this is done, let’s get back to our main goal. We will start by adding Unity in all of the modules through NuGet.
 
Refer to the image below.
 
 
Let's get into the code. Add Interface into DataAccess module and name it IProductDetails (our injector interface).
  1. using DataAccProductDetailsess;    
  2. namespace DataAccess.Interfaces    
  3. {    
  4.     public interface IProductDetails    
  5.     {    
  6.     }    
  7. }   
Let’s have that entity class assume we’re getting data from the product table. We’re setting up values of properties into the constructor, as there is no database. Make sure you have the same name for class as the interface(without the prefix I) for ease of understanding, so we name our class ProductDetails (our dependency class).
  1. using DataAccess.Interfaces;      
  2.       
  3. namespace DataAccProductDetailsess      
  4. {      
  5.     public class ProductDetails : IProductDetails      
  6.     {      
  7.         public string ProductName { getset; }      
  8.         public double ProductPrice { getset; }      
  9.         public int ProductQuantity { getset; }      
  10.       
  11.         public ProductDetails()      
  12.         {      
  13.             ProductName = "IPhone 11";      
  14.             ProductPrice = 100000;      
  15.             ProductQuantity = 1;      
  16.         }       
  17.        /// <summary>    
  18.        /// Get properties values assigned in constructor    
  19.        /// </summary>    
  20.        /// <returns>Returns this object</returns>    
  21.         public ProductDetails GetProductDetails()      
  22.         {      
  23.             return this;      
  24.         }      
  25.     }      
  26. }   
Let’s have one abstract method in IProductDetails Interface, which will return Product Details.
 
Update your interface as follow. (Refer to the bold part.)
  1. public interface IProductDetails    
  2.     {    
  3.         ProductDetails GetProductDetails();    
  4.     }   
Add class in DataAccess Module and name it ProductDataAccess (our dependent class).
  1. namespace DataAccess    
  2. {    
  3.     public class ProductDataAccess    
  4.     {    
  5.     }    
  6. }   
Now we want to fetch data from entity class, so what do we so? Generally, we create “Has-A” relationship with 2 classes. If class ProductDetails’s implementation changes, so does the source code. This is the Problem with Snapdragon and MediaTek implementation.
 
The following code show implementation without DI:
  1.     public class ProductDataAccess {     
  2.   
  3.         #region Properties      
  4.         //Has-ARelationship      
  5.         public ProductDetails ProductData;      
  6.         #endregion      
  7.   
  8.         #region Constructor      
  9.         public ProductDataAccess(IProductDetails _productDetails)      
  10.         {      
  11.         //here we have problem      
  12.         ProductData = new ProductDetails();      
  13.         }      
  14.         #endregion      
  15. }   
In the above code, ProductDataAccess class is dependent on ProductDetail class. We need to pass the reference at run time, rather than having it hard coded at compile time.
 
Solution: Update the ProductDataAccess. As you see, in order to get data from Entity class we’re fetching it from Interface, rather than having to create an object of ProductDetail class with New operator.
 
It may look like this:
 
1. Constructor Injection
  1. using DataAccess.Interfaces;    
  2. using DataAccProductDetailsess;    
  3. using System;    
  4. using Unity;    
  5.     
  6. namespace DataAccess    
  7. {    
  8.     public class ProductDataAccess     
  9.     {    
  10.         #region Dependancy variables    
  11.         public IProductDetails ProductDetails;    
  12.         #endregion    
  13.   
  14.         #region Constructor    
  15.         public ProductDataAccess(IProductDetails _productDetails)    
  16.         {    
  17.             this.ProductDetails = _productDetails;    
  18.             ProductDetails.GetProductDetails();    
  19.         }    
  20.         #endregion    
  21.   
  22.         #region Method Injection    
  23.     
  24.         public void PrintProductDetails()    
  25.         {   
  26.             ProductDetails ProdDetails = ProductDetails.GetProductDetails();    
  27.             Console.WriteLine("***********************************Receipt***********************************");    
  28.             Console.WriteLine("                Product :"+ ProdDetails.ProductName);    
  29.             Console.WriteLine("                Price   :" + ProdDetails.ProductPrice);    
  30.             Console.WriteLine("                Quantity:" + ProdDetails.ProductQuantity);    
  31.             Console.WriteLine("******************Thank you for shopping with Us !!!!!***********************");    
  32.         }    
  33.         #endregion    
  34.     }    
  35. }   
In the above code, the ProductDataAccess class doesn't depend on ProductDetail’s single implementation as we pass the reference at run time.
 
Register and Resolve: Container must provide a way to register and resolve dependencies. Unity container provides 2 methods to take care of the problem RegisterType() and Resolve().
 
Let’s move to our beloved presentation module.
Rename the program class ProductPresentation and add the following code. Create a UnityContainer object coming from namespace using Unity. Use RegisterType method as follows, you can see it takes “Generics”. First is the interface and second is your Class. By doing this UnityContainer would know where to look for dependencies. Use Resolve method which takes generics as parameter. And we’re solving dependency rather than creating an object.
  1. using DataAccess;    
  2. using DataAccess.Interfaces;    
  3. using DataAccProductDetailsess;    
  4. using System;    
  5. using Unity;    
  6.     
  7. namespace Presentation    
  8. {    
  9.     class ProductPresentation    
  10.     {    
  11.         static void Main(string[] args)    
  12.         {    
  13.             Console.WriteLine("3 layered architecture");    
  14.             UnityContainer container = new UnityContainer();    
  15.             container.RegisterType<IProductDetails, ProductDetails>();    
  16.     
  17.             ProductDataAccess  prodDetails = container.Resolve<ProductDataAccess>();    
  18.             prodDetails.PrintProductDetails();    
  19.             Console.ReadLine();    
  20.         }    
  21.     }    
  22. }  
Go ahead and build your project and run it. You’ll get following output.
 
There you go, now the classes are loosely coupled and more scalable. 
 
2. Property – Setter Dependency Injection.
 
Until now, we were passing a dependency using the constructor. What if we don’t even want that? No worries, Property DI to the rescue.
 
Add a property with Dependency attribute in the ProductDataAccess class
  1.  #region Dependant Properties    
  2.  [Dependency]    
  3.  public IProductDetails productData { getset; }    
  4.  #endregion    
  5.   
  6.  /// <summary>    
  7.  /// This method prints product details using Property DI    
  8. /// </summary>    
  9.  public void PrintProductDetailsWithPropDI()    
  10.  {    
  11.      ProductDetails ProdDetails = productData.GetProductDetails();    
  12.      Console.WriteLine("***********************************Receipt From Property/Setter DI***********************************");    
  13.      Console.WriteLine("                Product :" + ProdDetails.ProductName);    
  14.      Console.WriteLine("                Price   :" + ProdDetails.ProductPrice);    
  15.      Console.WriteLine("                Quantity:" + ProdDetails.ProductQuantity);    
  16.      Console.WriteLine("******************Thank you for shopping with Us !!!!!***********************");     
  17.  }   
In order to call this method, make changes in the ProductPresentation class.
 
First, comment Constructor DI call and add the following line:
  1. //prodDetails.PrintProductDetails();    
  2. prodDetails.PrintProductDetailsWithPropDI();   
Your output will look like this:
 
 
 
3. Method Injection: Injecting dependency using method.
 
Add these properties in ProductDataAccess class
  1. #region Method DI Properties    
  2.   /// <summary>    
  3.   /// This property is for Method DI    
  4.   /// </summary>    
  5.   private IProductDetails productDataUsingMethodDI = null;    
  6.   
  7.   /// <summary>    
  8.   /// Variable to assign product details    
  9.   /// </summary>    
  10.   public ProductDetails ProdDetails { getset; }    
  11.   #endregion   
Add these 2 methods. One is for injection and another is for printing details of the product As you can see, we are using the InjectionMethod attribute to tell container which method to look for. We're not going to call the method AssignProductDetailsWithMethodDI() explicitly, rather the attribute tells the complier where to look. 
  1. /// <summary>    
  2. /// This method injects product details using Method DI    
  3. /// </summary>    
  4. [InjectionMethod]    
  5. public void AssignProductDetailsWithMethodDI(IProductDetails _productDetails)    
  6. {    
  7.     productDataUsingMethodDI = _productDetails;    
  8.     ProdDetails = productDataUsingMethodDI.GetProductDetails();    
  9. }    
  10. /// <summary>  
  11. /// This method prints product details using Method DI    
  12. /// </summary>    
  13. public void PrintProductDetailsWithMethodDI()    
  14. {    
  15.         
  16.     Console.WriteLine("***********************************Receipt From Methos DI***********************************");    
  17.     Console.WriteLine("                Product :" + ProdDetails.ProductName);    
  18.     Console.WriteLine("                Price   :" + ProdDetails.ProductPrice);    
  19.     Console.WriteLine("                Quantity:" + ProdDetails.ProductQuantity);    
  20.     Console.WriteLine("******************Thank you for shopping with Us !!!!!***********************");    
  21. }  
 Let’s call this method as well from our main(). Open ProductPresentation class, and comment the first 2 method calls, then and add third one.
  1. //prodDetails.PrintProductDetails();    
  2. //prodDetails.PrintProductDetailsWithPropDI();    
  3. prodDetails.PrintProductDetailsWithMethodDI();   
The output of following code will look like this: 
 
 
4. Let us uncomment everything and run project with all types of dependencies.
 
The Final ProductDataAccess class will look like this with all types of dependencies designed.
  1. using DataAccess.Interfaces;    
  2. using DataAccProductDetailsess;    
  3. using System;    
  4. using Unity;    
  5.     
  6. namespace DataAccess    
  7. {    
  8.     public class ProductDataAccess     
  9.     {    
  10.         #region Constructor DIProperties    
  11.         /// <summary>    
  12.         /// This property is for Constructor DI    
  13.         /// </summary>    
  14.         public IProductDetails ProductDetails;    
  15.         #endregion    
  16.   
  17.         #region Dependant DISetter Properties    
  18.         /// <summary>    
  19.         /// This property is for Setter DI    
  20.         /// </summary>    
  21.         [Dependency]    
  22.         public IProductDetails productData { getset; }    
  23.         #endregion    
  24.   
  25.         #region Method DI Properties    
  26.         /// <summary>    
  27.         /// This property is for Method DI    
  28.         /// </summary>    
  29.         private IProductDetails productDataUsingMethodDI = null;    
  30.     
  31.         /// <summary>    
  32.         /// Variable to assign product details    
  33.         /// </summary>    
  34.         public ProductDetails ProdDetails { getset; }    
  35.         #endregion    
  36.   
  37.         #region Constructor    
  38.         /// <summary>    
  39.         /// Injects dependancy using constructor    
  40.         /// </summary>    
  41.         /// <param name="_productDetails"></param>    
  42.         public ProductDataAccess(IProductDetails _productDetails)    
  43.         {    
  44.             this.ProductDetails = _productDetails;    
  45.             ProductDetails.GetProductDetails();    
  46.         }    
  47.         #endregion    
  48.   
  49.         #region Method Injection    
  50.         /// <summary>    
  51.         /// This method prints product details using Constructor DI    
  52.         /// </summary>    
  53.         public void PrintProductDetails()    
  54.         {    
  55.             ProductDetails ProdDetails = ProductDetails.GetProductDetails();    
  56.             Console.WriteLine("***********************************Receipt From Construcor DI**************************************");    
  57.             Console.WriteLine("                Product :"+ ProdDetails.ProductName);    
  58.             Console.WriteLine("                Price   :" + ProdDetails.ProductPrice);    
  59.             Console.WriteLine("                Quantity:" + ProdDetails.ProductQuantity);    
  60.             Console.WriteLine("******************Thank you for shopping with Us !!!!!***********************");    
  61.     
  62.         }    
  63.     
  64.         /// <summary>    
  65.         /// This method prints product details using Property DI    
  66.         /// </summary>    
  67.         public void PrintProductDetailsWithPropDI()    
  68.         {    
  69.             ProductDetails ProdDetails = productData.GetProductDetails();    
  70.             Console.WriteLine("***********************************Receipt From Property/Setter DI***********************************");    
  71.             Console.WriteLine("                Product :" + ProdDetails.ProductName);    
  72.             Console.WriteLine("                Price   :" + ProdDetails.ProductPrice);    
  73.             Console.WriteLine("                Quantity:" + ProdDetails.ProductQuantity);    
  74.             Console.WriteLine("******************Thank you for shopping with Us !!!!!***********************");    
  75.         }    
  76.     
  77.         /// <summary>    
  78.         /// This method injects product details using Method DI    
  79.         /// </summary>    
  80.         [InjectionMethod]    
  81.         public void AssignProductDetailsWithMethodDI(IProductDetails _productDetails)    
  82.         {    
  83.             productDataUsingMethodDI = _productDetails;    
  84.             ProdDetails = productDataUsingMethodDI.GetProductDetails();    
  85.         }    
  86.     
  87.         /// <summary>    
  88.         /// This method prints product details using Method DI    
  89.         /// </summary>    
  90.         public void PrintProductDetailsWithMethodDI()    
  91.         {                 
  92.             Console.WriteLine("***********************************Receipt From Methos DI***********************************");    
  93.             Console.WriteLine("                Product :" + ProdDetails.ProductName);    
  94.             Console.WriteLine("                Price   :" + ProdDetails.ProductPrice);    
  95.             Console.WriteLine("                Quantity:" + ProdDetails.ProductQuantity);    
  96.             Console.WriteLine("******************Thank you for shopping with Us !!!!!***********************");    
  97.         }    
  98.         #endregion    
  99.     }    
  100. }   
 The ProductPresentation class would look like this with all types of dependencies designed.
  1. using DataAccess;    
  2. using DataAccess.Interfaces;    
  3. using DataAccProductDetailsess;    
  4. using System;    
  5. using Unity;    
  6.     
  7. namespace Presentation    
  8. {    
  9.     class ProductPresentation    
  10.     {    
  11.         static void Main(string[] args)    
  12.         {    
  13.             Console.WriteLine("3 layered architecture");    
  14.             UnityContainer container = new UnityContainer();    
  15.             container.RegisterType<IProductDetails, ProductDetails>();    
  16.     
  17.             ProductDataAccess prodDetails = container.Resolve<ProductDataAccess>();    
  18.             prodDetails.PrintProductDetails();    
  19.             Console.WriteLine();    
  20.             prodDetails.PrintProductDetailsWithPropDI();    
  21.             Console.WriteLine();    
  22.             prodDetails.PrintProductDetailsWithMethodDI();    
  23.             Console.ReadLine();    
  24.         }    
  25.     }    
  26. }   
Let's see what the output would look like with all types of dependencies implemented.
 
Note: We haven’t made much of use of the BusinessLogic layer. The point of keeping that module is so we can have Higher level modules communicating with the lower-level modules by having a loosely coupled relationship.
 
Download the project to get source code for a better understanding.
 
Thank you so much for visiting this blog, I hope you were helped by this. If you have any queries, please connect with me.
 
Happy Coding. Have a good day :)
Next Recommended Reading Value And Reference Type In OOPS