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).
- using DataAccProductDetailsess;
- namespace DataAccess.Interfaces
- {
- public interface IProductDetails
- {
- }
- }
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).
- using DataAccess.Interfaces;
-
- namespace DataAccProductDetailsess
- {
- public class ProductDetails : IProductDetails
- {
- public string ProductName { get; set; }
- public double ProductPrice { get; set; }
- public int ProductQuantity { get; set; }
-
- public ProductDetails()
- {
- ProductName = "IPhone 11";
- ProductPrice = 100000;
- ProductQuantity = 1;
- }
-
-
-
-
- public ProductDetails GetProductDetails()
- {
- return this;
- }
- }
- }
Let’s have one abstract method in IProductDetails Interface, which will return Product Details.
Update your interface as follow. (Refer to the bold part.)
- public interface IProductDetails
- {
- ProductDetails GetProductDetails();
- }
Add class in DataAccess Module and name it ProductDataAccess (our dependent class).
- namespace DataAccess
- {
- public class ProductDataAccess
- {
- }
- }
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:
- public class ProductDataAccess {
-
- #region Properties
-
- public ProductDetails ProductData;
- #endregion
-
- #region Constructor
- public ProductDataAccess(IProductDetails _productDetails)
- {
-
- ProductData = new ProductDetails();
- }
- #endregion
- }
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
- using DataAccess.Interfaces;
- using DataAccProductDetailsess;
- using System;
- using Unity;
-
- namespace DataAccess
- {
- public class ProductDataAccess
- {
- #region Dependancy variables
- public IProductDetails ProductDetails;
- #endregion
-
- #region Constructor
- public ProductDataAccess(IProductDetails _productDetails)
- {
- this.ProductDetails = _productDetails;
- ProductDetails.GetProductDetails();
- }
- #endregion
-
- #region Method Injection
-
- public void PrintProductDetails()
- {
- ProductDetails ProdDetails = ProductDetails.GetProductDetails();
- Console.WriteLine("***********************************Receipt***********************************");
- Console.WriteLine(" Product :"+ ProdDetails.ProductName);
- Console.WriteLine(" Price :" + ProdDetails.ProductPrice);
- Console.WriteLine(" Quantity:" + ProdDetails.ProductQuantity);
- Console.WriteLine("******************Thank you for shopping with Us !!!!!***********************");
- }
- #endregion
- }
- }
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.
- using DataAccess;
- using DataAccess.Interfaces;
- using DataAccProductDetailsess;
- using System;
- using Unity;
-
- namespace Presentation
- {
- class ProductPresentation
- {
- static void Main(string[] args)
- {
- Console.WriteLine("3 layered architecture");
- UnityContainer container = new UnityContainer();
- container.RegisterType<IProductDetails, ProductDetails>();
-
- ProductDataAccess prodDetails = container.Resolve<ProductDataAccess>();
- prodDetails.PrintProductDetails();
- Console.ReadLine();
- }
- }
- }
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
- #region Dependant Properties
- [Dependency]
- public IProductDetails productData { get; set; }
- #endregion
-
-
-
-
- public void PrintProductDetailsWithPropDI()
- {
- ProductDetails ProdDetails = productData.GetProductDetails();
- Console.WriteLine("***********************************Receipt From Property/Setter DI***********************************");
- Console.WriteLine(" Product :" + ProdDetails.ProductName);
- Console.WriteLine(" Price :" + ProdDetails.ProductPrice);
- Console.WriteLine(" Quantity:" + ProdDetails.ProductQuantity);
- Console.WriteLine("******************Thank you for shopping with Us !!!!!***********************");
- }
In order to call this method, make changes in the ProductPresentation class.
First, comment Constructor DI call and add the following line:
-
- prodDetails.PrintProductDetailsWithPropDI();
Your output will look like this:
3. Method Injection: Injecting dependency using method.
Add these properties in ProductDataAccess class
- #region Method DI Properties
-
-
-
- private IProductDetails productDataUsingMethodDI = null;
-
-
-
-
- public ProductDetails ProdDetails { get; set; }
- #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.
-
-
-
- [InjectionMethod]
- public void AssignProductDetailsWithMethodDI(IProductDetails _productDetails)
- {
- productDataUsingMethodDI = _productDetails;
- ProdDetails = productDataUsingMethodDI.GetProductDetails();
- }
-
-
-
- public void PrintProductDetailsWithMethodDI()
- {
-
- Console.WriteLine("***********************************Receipt From Methos DI***********************************");
- Console.WriteLine(" Product :" + ProdDetails.ProductName);
- Console.WriteLine(" Price :" + ProdDetails.ProductPrice);
- Console.WriteLine(" Quantity:" + ProdDetails.ProductQuantity);
- Console.WriteLine("******************Thank you for shopping with Us !!!!!***********************");
- }
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.
-
-
- prodDetails.PrintProductDetailsWithMethodDI();
The output of following code will look like this: