Introduction
Dependency Injection (DI) is a pattern where objects are not responsible for creating their own dependencies. Dependency injection is a way to remove hard-coded dependencies among objects, making it easier to replace an object's dependencies, either for testing (using mock objects in unit test) or to change run-time behavior.
Before understanding Dependency Injection you should be familiar with the two concepts of Object Oriented Programming, one is tight coupling and another is loose coupling, so let's see each one by one.
Tight Coupling: When a class is dependent on a concrete dependency, it is said to be tightly coupled to that class. A tightly coupled object is dependent on another object; that means changing one object in a tightly coupled application often requires changes to a number of other objects. It is not difficult when an application is small but in an enterprise level application it is too difficult to make the changes.
Loose Coupling: It means two objects are independent and an object can use another object without being dependent on it. It is a design goal that seeks to reduce the inter- dependencies among components of a system with the goal of reducing the risk that changes in one component will require changes in any other component.
Now in short, Dependency Injection is a pattern that makes objects loosely coupled instead of tightly coupled. In this article you will be the first to introduce tight coupling and thereafter you will introduce loose coupling using the Constructor Dependency Injection Pattern. So let's see that.
Using the Code
To understand the concept of Dependency Injection, create an example that is the Error Log Management. The Error Log Management example describes how to create an error log for the application. There are two types of log management, one is using a text file and the other is uses an event viewer.
First of all create an interface IErrorLogger that has one method to write the error log. This interface will be inherited by other log classes.
- using System;
-
- namespace DependencyInjection
- {
- public interface IErrorLogger
- {
- void LogMessage(Exception ex);
- }
- }
Now create a class, FileLogger class, that inherits the IErrorLogger interface. This class writes an error log message to a text file.
- using System;
- using System.Configuration;
- using System.IO;
-
- namespace DependencyInjection
- {
- public class FileLogger : IErrorLogger
- {
- public void LogMessage(Exception ex)
- {
- string folderPath = ConfigurationManager.AppSettings["ErrorFolder"];
- if (!(Directory.Exists(folderPath)))
- {
- Directory.CreateDirectory(folderPath);
- }
- FileStream objFileStrome = new FileStream(folderPath + "errlog.txt", FileMode.Append, FileAccess.Write);
- StreamWriter objStreamWriter = new StreamWriter(objFileStrome);
- objStreamWriter.Write("Message: " + ex.Message);
- objStreamWriter.Write("StackTrace: " + ex.StackTrace);
- objStreamWriter.Write("Date/Time: " + DateTime.Now.ToString());
- objStreamWriter.Write("============================================");
- objStreamWriter.Close();
- objFileStrome.Close();
- }
- }
- }
Now create a class EventViewerLogger class that inherits the IErrorLogger interface. This class writes an error log message in the event viewer.
- using System;
- using System.Configuration;
- using System.Diagnostics;
-
- namespace DependencyInjection
- {
- public class EventViewerLogger : IErrorLogger
- {
- public void LogMessage(Exception ex)
- {
- EventLog objEventLog = new EventLog();
- string sourceName = ConfigurationManager.AppSettings["App"];
- string logName = ConfigurationManager.AppSettings["LogName"];
- if (!(EventLog.SourceExists(sourceName)))
- {
- EventLog.CreateEventSource(sourceName, logName);
- }
- objEventLog.Source = sourceName;
- string message = String.Format("Message: {0} \n StackTrace: {1} \n Date/Time: {2} ", ex.Message, ex.StackTrace, DateTime.Now.ToString());
- objEventLog.WriteEntry(message, EventLogEntryType.Error);
- }
- }
- }
Now we create another class to understand the concept of tight coupling. The Operation class has an interface, an IErrorLogger instance, created by the FileLogger class. In other words the Operation class object is tightly coupled with the FileLogger class.
- using System;
-
- namespace DependencyInjection
- {
- public class Operation
- {
- IErrorLogger logger = new FileLogger();
- public void Division()
- {
- try
- {
- int firstNumber = 15, secondNumber = 0, result;
- result = firstNumber / secondNumber;
- Console.WriteLine("Result is :{0}", result);
- }
- catch (DivideByZeroException ex)
- {
- logger.LogMessage(ex);
- }
- }
- }
- }
The code above works, but it's not the best design because it is tightly coupled with FileLogger. When you want to use another IErrorLogger implementation class then you need to change it. That is not depending on the Open Closed Principle of Object Oriented Programming.
Now call your class method in your application startup class and you get a log in the text file so your start up class code is below.
- using System;
- namespace DependencyInjection
- {
- class Program
- {
- static void Main(string[] args)
- {
- Operation objOperation = new Operation();
- objOperation.Division();
- Console.Read();
- }
- }
- }
Let's run the application. You will get an exception in the log file such as in Figure 1.1.
Figure 1.1 Error Log in txt File
To solve this problem you should use constructor dependency injection in which an IErrorLogger is injected into the object.
Constructor Dependency Injection PatternThis is the most commonly used Dependency Pattern in Object Oriented Programming. The Constructor Injection uses a parameter to inject dependencies so there is normally one parameterized constructor always. So in this constructor dependency the object has no default constructor and you need to pass specified values at the time of creation to initiate the object.
You can say that your design is loosely coupled with the use of constructor dependency injection.
Now create a class OperationEvent. That class has a parameter constructor. This constructor will be used to inject dependencies in the object. Let's see the following code.
- using System;
-
- namespace DependencyInjection
- {
- public class OperationEvent
- {
- IErrorLogger logger;
-
- public OperationEvent(IErrorLogger logger)
- {
- this.logger = logger;
- }
-
- public void Division()
- {
- try
- {
- int firstNumber = 15, secondNumber = 0, result;
- result = firstNumber / secondNumber;
- Console.WriteLine("Result is :{0}", result);
- }
- catch (DivideByZeroException ex)
- {
- logger.LogMessage(ex);
- }
- }
- }
- }
By the preceding you have noticed that an OperationEvent object is neither dependent on an FileLogger object nor an EventViewerLogger object so you can inject either FileLogger dependencies or EventViwerLogger dependencies at run time. Let's see the startup class in which we inject EventViwerLogger dependencies in an OperationEvent object using constructor dependency injection.
- using System;
-
- namespace DependencyInjection
- {
- class Program
- {
- static void Main(string[] args)
- {
- OperationEvent objOperationEvent = new OperationEvent(new EventViewerLogger());
- objOperationEvent.Division();
- Console.Read();
- }
- }
- }
Let's run the application and you get the results as in Figure 1.2 .
Figure 1.2 Log Errors in Event Viewer.
ConclusionThis article has introduced a basic object oriented concept such as tight coupling and loose coupling. I hope you also get an idea of how to manage your application error log and finally you have learned about Constructor Dependency Injection Pattern. I hope it will be helpful for you and if you have any doubt or feedback then post your comments here or you can directly connect to me by
https://twitter.com/ss_shekhawat.