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 tip, 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 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.
Hide Copy Code
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.
Hide Copy Code
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 an
EventViewerLogger class that inherits the
IErrorLogger interface. This class writes an error log message in the event viewer.
Hide Copy Code
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.
Hide Copy Code
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:
Hide Copy Code
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 Pattern
This 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.
Hide Copy 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
EventViewerLogger dependencies in an
OperationEvent object using constructor dependency injection.
Hide Copy Code
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.
Source :Code Project..!!