Dependency Injection In C#

Dependency Injection

As per the dependency inversion principle, high level modules should not be directly dependent on low level modules; rather they should depend on abstraction. Ex - 

  1. public class DBLog {  
  2.     public void LogMe() {  
  3.         Console.WriteLine("DB Logging on");  
  4.     }  
  5. }  
  6. class Program {  
  7.     void Main() {  
  8.         DBLog obj = new DBLog();  
  9.         obj.logMe();  
  10.         Console.ReadLine();  
  11.     }  
  12. }  

If you see in the above example, class DBLog is responsible for providing the logging and this class is being used in program class, which is actually logging.

In the above example, Program class (which is a high level module) is using DBLog class directly that means both of these classes have tight coupling to each other. There is no problem in this implementation, however it has a big design problem.

If tomorrow, the client decides to have logging in files (file logging) or in event viewer, what are our options? At this moment, our option is to only change the Program class and start using FileLog or EventLog class. It will change the code of Program class and also, you can’t test Program class independently because it depends upon DBLog class so it also adds overhead of testing.

What is the solution?

As I mentioned earlier, the solution to this is to use DI principle (‘D’ of SOLID principle). As per ‘D’, two independent objects (entity) should not depend upon each other directly, rather it should depend upon abstraction. Which means 1. Create an interface 2. Put logMe() in the interface 3.Derive the class with that interface 4. Use that interface to instantiate DBLog class.

E.g

  1. public interface Ilogger {  
  2.     void logMe();  
  3. }  
  4. public class DBLog: Ilogger {  
  5.     public void logMe() {  
  6.         Console.WriteLine("DB Logging on");  
  7.     }  
  8. }  
  9. class Program {  
  10.     void Main() {  
  11.         Ilogger obj = new DBLog();  
  12.         obj.logMe();  
  13.         Console.ReadLine();  
  14.     }  

This approach did something but partially. It is not the real solution to our problem. Does it now follow the “D rule of SOLID principle”? The answer to both of these questions is “No”.

So, what next? Yes, DI will resolve it.

Dependency Injection

DI is a software design pattern which is used to remove dependency of objects and creates the independent business objects. As it is clear from its name, high level module will inject the interface or abstraction of the low level object. This injection can be done by using any one of these,

  1. Constructor
  2. Properties
  3. Methods.

It is also very useful for Test Driven Development approach. Since the components or objects are independent, they can be tested independently.

Resolve dependency with the help of constructor or Constructor Injection 

  1. public interface Ilogger {  
  2.     void logMe();  
  3. }  
  4. public class DBLog: Ilogger {  
  5.     public void logMe() {  
  6.         Console.WriteLine("DB Logging on");  
  7.     }  
  8. }  
  9. public class FileLog: Ilogger {  
  10.     public void logMe() {  
  11.         Console.WriteLine("DB Logging on");  
  12.     }  
  13. }  
  14. class Program {  
  15.     Program(Ilogger log) {  
  16.         log.logMe();  
  17.     }  
  18.     void Caller() {  
  19.         Program p = new Program(new DBLog());  
  20.     }  
  21. }   

In the above program, as you can see, the Program class constructor is having parameter of ILogger interface. ILogger interface is being implemented in DBLog, FileLog both classes. Since interface is being implemented in both classes so any of the class object which implements ILogger interface can be passed to this constructor. In this particular example Program class is working as a factory class to create the object. So any caller class which want logging , will call the constructor of Program class by providing the reference of required object.

One more point to note here is, though I have passed instance of DBLog class to the parameter but you can also read this information from your config file or DB. So If we want to change the logging from DBLog to FIleLog we can do so , without even a single line of code change. e.g. -

  1. string str = read the value of DB or config  
  2. Type instanceType = Type.GetType(str)  
  3. Ilogger log = (Ilogger) Activator.CreateInstance(instanceType);   

Resolve dependency with the help of properties or Property Injection

If responsibility of selection of objects are present in two separate classes then we may want to use property injection. 

  1. class FactoryClass {  
  2.     Ilogger Ilog;  
  3.     public Ilogger setLog {  
  4.         get {  
  5.             return Ilog;  
  6.         }  
  7.         set {  
  8.             Ilog = value;  
  9.         }  
  10.     }  
  11.     public void CallMe() {  
  12.         Ilog.logMe();  
  13.     }  
  14. }  

In the above example, we have created a Factory class which is having a setter property. This class also has a method CallMe() which is calling the logger. It will call the logger based on the object which has got set in the property.

Resolve dependency with the help of method or Method Injection

Another way to inject dependency is by passing the object in a method. So, in method injection, we pass the object of the concrete class into the method of the dependent class which invokes the action.

Few example to show DI.

Interface

  1. namespace Dependency_Injection {  
  2.     public interface IEmployee {  
  3.         bool IsEmployeeExists(string EmpName);  
  4.         string InsertEmployeeInDB(string EmpName);  
  5.     }  
  6. }  

DB Mapper(DAL)

  1. namespace Dependency_Injection {  
  2.     public class EmployeeDatabaseMapper: IEmployee {  
  3.         public bool IsEmployeeExists(string EmpName) {  
  4.             bool isExists = false;  
  5.             if (string.IsNullOrEmpty(EmpName)) {  
  6.                 throw new ArgumentException("Emp Name can't be null");  
  7.             }  
  8.             // check the existence of employee in DB and set the IsExists accordingly.  
  9.             return isExists;  
  10.         }  
  11.         public string InsertEmployeeInDB(string EmpName) {  
  12.             if (string.IsNullOrEmpty(EmpName)) {  
  13.                 throw new ArgumentException("Emp Name can't be null");  
  14.             }  
  15.             // insert the details in database and return EmpName  
  16.             return EmpName;  
  17.         }  
  18.     }  
  19. }  

Employee class (client)

  1. namespace Dependency_Injection {  
  2.     public class Employee {  
  3.         public string CreateNew(string strName, IEmployee emp) {  
  4.             if (emp == null) {  
  5.                 emp = new EmployeeDatabaseMapper();  
  6.             }  
  7.             if (string.IsNullOrEmpty(strName)) throw new ArgumentException("Emp Name can't be null");  
  8.             if (emp.IsEmployeeExists(strName)) throw new ArgumentException("Emp Name already exists");  
  9.             return emp.InsertEmployeeInDB(strName);  
  10.         }  
  11.     }  

Below is the structure of a sample project to show each layer and their brief functionality.

Conclusion

Dependency Injection is a solution to a particular set of problems but it does not mean that you need to inject every dependency. Before injecting dependency, you need to analyze that by doing so, am I making class more testable and/or more maintainable.

Two main things you typically want to inject into a class are - Services and Configuration values.

Services are those components that will do something for your caller class; it is generally service, abstraction or interface.

Configuration values are the ones which you do not want to hardcode and do not want to read inside the class rather you will pass it.

By using DI, you will reduce class coupling, Increases code reusability, Improves code maintainability and Improves application testing.

How it will help in improving application testing

If we consider the above given example only and if we do not use interface, then we will not be able to test Employee class independently. The reason is that Employee class interacts with DAL (EmployeeDatabaseMapper) and DAL interacts with DB. So, without involving the DB, Employee class can’t be tested. Since we have now resolved the dependency by adding a layer of abstraction (Interface), so any class which implements the interface can be passed in CreateNew() method of Employee class.

For testing, we can create any mock class and have it implement with Interface. That class can work as a Database repository and hold few temp data and testing team can use that to test it in place of database.
Ebook Download
View all
Learn
View all