Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dependency Injection using Simple Real Time Application in C# (Part I)

0.00/5 (No votes)
27 Feb 2021 1  
A simple article to understand the concept of dependency injection using C#
Dependency Injection is explained in this article in a very simple manner for all levels of developers to understand it properly.

Introduction

Dependency Injection is a concept that most of the developers struggle to grasp at first. Honestly, it is kind of intimidating. I surely took a big chunk of time to understand it properly. Here, in this article, I’ll break down the concept to help my fellow developers to understand DI and why we need it.

Points to Focus

  1. What is dependency injection?
  2. Why do we need DI? What problem does it solve?
  3. How do we implement it?

I could go on with more questions but, I won’t, because I don’t want to complicate things. Let’s learn things clearly in the simplest way possible.

Sample Project: Initial Steps

Let’s write a simple console application program without dependency injection with one rule in order to understand the problem.

Rule: The application code must be testable. (Testable = The code must facilitate Unit Test implementation)

Step 1: Open Visual Studio.

Step 2: Create a new console project. Name the project DependencyInjection.

Step 3: Add a new code file to the project. Name the file OrderProcessor.cs.

Think of OrderProcessor class that provides functionality to process Orders by having them as argument.

So far, simple enough.

(Imagine it as a very core level implementation of Amazon Order Management system. Most of us have placed orders in the Amazon shopping platform.)

In order to have an OrderProcessor mechanism, we need to have an Order at first. So, we need two classes now.

Classes to Implement

  • Order: a class with properties of an order. In other words, it is a model. (Model = Class with just properties, commonly used to resemble a table in the database).
  • OrderProcessor: a class with an Order object processing mechanism.
//Order.cs

namespace DependencyInjection
{
    public class Order
    {
        public int Id {get;set;}
        public int CustomerId {get; set;}
        public decimal TotalAmount {get;set;}
        public int Status {get;set;}
        public List<OrderDetail> Details {get;set;}
    }
}

Hold on, what is that OrderDetail? Introducing new class: OrderDetail.

  • OrderDetail: a class that contains product information placed within that a order.

Now, Products are also individual objects, so we need to create a model for Product as well.

  • Product: a class with properties of a regular product. (example, Pen is a product).
//OrderDetails.cs

using System.Collections.Generic;

namespace DependencyInjection
{
    public class OrderDetail
    {
        public int OrderDetailId {get;set;}
        public int OrderId {get;set;}
        public Product Item{get;set;}
        public int Quantity {get;set;}
    }
}
//Product.cs

namespace DependencyInjection
{
    public class Product
    {
        public int Id{get;set;}
        public string ProductName {get; set;}
        public decimal Price {get;set;}
    }
}

Now, our basic models are ready. Let’s picture them.

Step 4: Create a folder named Models in the project and add the above devised models inside that folder.

Step 5: Let’s implement OrderProcessor class.

Requirement:

  1. OrderProcessor should take an Order object as an input and process it.
  2. OrderProcessor should validate the order details using a ValidateOrder class.
  3. OrderProcessor should have a capability to print processed order details using PrintOrder class.

Introducing new classes: ValidateOrder, PrintOrder

  • ValidateOrder: a class with validating the order mechanism with having Order object as input.
  • PrintOrder: a class with capability to print the detailed information about the given Order object.
//OrderProcessor.cs

using System;

namespace DependencyInjection
{
    public class OrderProcessor
    {
        Order order;

        public OrderProcessor(Order order)
        {
            this.order = order;
        }
        public void ProcessOrder()
        {
            Console.WriteLine("Process Started.");

            ValidateOrder validateOrder = new ValidateOrder();

            if (validateOrder.CheckAllDetails(order))
            {
                //Processing order.
                foreach (var orderDetail in order.Details)
                {
                    order.TotalAmount += orderDetail.Item.Price *
                                         orderDetail.Quantity;
                }

                PrintOrder printOrder = new PrintOrder();
                printOrder.PrintAllDetails(order);

                Console.WriteLine($"Order : {order.Id} has been processed.");
            }
            else
            {
                Console.WriteLine($"Order : {order.Id} processing failed.");
            }

            Console.WriteLine("Process Ended.");
        }
    }
}
//ValidateOrder.cs

namespace DependencyInjection
{
    public class ValidateOrder
    {
        public bool CheckAllDetails(Order order)
        {
            return order.Id > 0;
        }
    }
}
//PrintOrder.cs

using System;

namespace DependencyInjection
{
    public class PrintOrder
    {
        public void PrintAllDetails(Order order)
        {
            Console.WriteLine($"OrderID : {order.Id}");
            Console.WriteLine($"CustomerID : {order.CustomerId}");
            
            foreach(var orderDetail in order.Details)
            {
                Console.WriteLine($"Product Name : {orderDetail.Item.ProductName}
                (Price : {orderDetail.Item.Price})(Quantity : { orderDetail.Quantity })");
            }

            Console.WriteLine($"TotalAmount : {order.TotalAmount}");
        }
    }
}

Let’s picture our worker classes.

The Main Program

Let’s take a look at our Main program:

//Program.cs

using System;
using System.Collections.Generic;

namespace DependencyInjection
{
    class Program
    {
        static void Main(string[] args)
        {
            //Products
            var productPen = new Product()
                             { Id = 1, ProductName = "Pen", Price = 10 };
            var productNotebook = new Product()
                             { Id = 2, ProductName = "Notebook", Price = 35 };

            //OrderDetail
            List<OrderDetail> orderDetails = new List<OrderDetail>();
            orderDetails.Add(new OrderDetail()
            {
                OrderDetailId = 1,
                OrderId = 1,
                Item = productPen,
                Quantity = 1
            });
            orderDetails.Add(new OrderDetail()
            {
                OrderDetailId = 2,
                OrderId = 1,
                Item = productNotebook,
                Quantity = 1
            });

            //Order
            Order order = new Order();
            order.Id = 1;
            order.Details = orderDetails;
            order.CustomerId = 1001;

            OrderProcessor orderProcessor = new OrderProcessor(order);
            orderProcessor.ProcessOrder();
        }
    }
}

Simply put, in our main program, a list of OrderDetail object is created and it is given to an Order object.

And by passing that order with details to the constructor of OrderProcessor, we’re processing the orders.

Output of this simple application looks like this:

Are we done yet? Nope. Remember the rule.

Rule: The application code must be testable. (Testable = The code must facilitate Unit Test implementation.)

We have created an application which works fine as per the requirement. But in order to confirm our code quality and ensure that it won’t break on different scenarios, we need to write UnitTest.

Tools are out there to apply test project in our solutions, but there is a catch. To apply UnitTest, the targeted code must be testable.

Is our code testable? Nope.

Why is it not a testable code? Because OrderProcessor class contains tightly coupled code.

What is a tightly coupled code? In simple terms, Code that contains direct instantiation of its dependency classes directly are called tightly coupled.

We say OrderProcessor contains tightly coupled code because of the following block of code it holds.

ValidateOrder validateOrder = new ValidateOrder(); //Dependency

PrintOrder printOrder = new PrintOrder();          //Dependency

OrderProcessor’s ProcessOrder method uses validateOrder and printOrder object.

Dependency Injection is a concept that helps us to convert the tightly coupled code to loosely coupled code and doing so it makes our solution, untestable to testable.

So far, we have successfully implemented a sample project to work on.

In Part II, the exact problem that DI helps us to resolve will be explained in detail. Be on the lookout for that.

At the top of this post, I've attached the full source code file for this article to play with. The same code will be updated in the next article, so just be aware that it is not the final version of the code for this article.

Thanks for reading. Never stop learning!

History

  • 27th February, 2021: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here