According to Wikipedia Dependency injection (DI) in Computer programming refers to the process of supplying an external dependency to a software component. In this post I will try to explain what dependency injects is and how can you create your own dependency injection container.
Let’s consider that you have a CustomerRepository which handles the operations on the Customer object.
public class CustomerRepository : IRepository<Customer>
{
public void Save(Customer item)
{
// save the customer
SqlDataAccess.Save(item);
}
}
The client uses the following code to call the CustomerRepository.
Customer customer = new Customer() { FirstName = "Mohammad", LastName = "Azam" };
CustomerRepository repository = new CustomerRepository();
repository.Save(customer);
The save method uses the SqlDataAccess service to persists the changes to the database. Everything works fine until someone introduced the OracleDataAccess service. Now, we have a choice between the SqlDataAccess data service or the OracleDataAccess service. This will change your design and we will need to introduce a service interface which will provide the implementation for the SqlDataService and OracleDataService.
Here is the interface IDataService:
public interface IDataService
{
int SaveCustomer(Customer customer);
}
SqlDataService and OracleDataService implement the IDataService interface.
The implementation for the CustomerRepository changed to the following:
public class CustomerRepository : IRepository<Customer>
{
private IDataService _dataService;
public CustomerRepository(IDataService dataService)
{
_dataService = dataService;
}
public void Save(Customer item)
{
// save the customer
_dataService.SaveCustomer(item);
}
}
The constructor of CustomerRepository now takes IDataService interface. This means that we can pass in the object which inherits from the IDataService interface.
Here is the client code that uses the OracleDataAccess for the CustomerRepository.
CustomerRepository repository = new CustomerRepository(new OracleDataService());
The above code shows that the CustomerRepository creation is dependent on the IDataService interface. This is a simple scenario hence it is handled in a simple manner. If the depth of dependencies is more then it gets very complex.
Now, let’s consider that CustomerRepository is also dependent on another interface, INotificationService. This will require changes in the CustomerRepository class.
public class CustomerRepository : IRepository<Customer>
{
private IDataService _dataService;
private INotificationService _notificationService;
public CustomerRepository(IDataService dataService)
{
_dataService = dataService;
}
public CustomerRepository(IDataService dataService, INotificationService notificationService)
{
_dataService = dataService;
_notificationService = notificationService;
}
public void Save(Customer item)
{
// save the customer
_dataService.SaveCustomer(item);
_notificationService.Notify();
}
}
Now, we need to create the INotificationService object before creating the CustomerRepository as the CustomerRepository depends on the INotificationService.
INotificationService faxService = new FaxNotificationService();
IDataService sqlDataService = new SqlDataService();
CustomerRepository repository = new CustomerRepository(sqlDataService, faxService);
The solution to this problem is to introduce “Dependency Injection” which will extract out the dependency from the object and put it in a different place, a container.
There are many different dependency injections frameworks available but in this post I will try to create a custom dependency container.
First we need to register our types for dependencies. Here is the register method which will be used to register the types.
private static Dictionary<string, string> _container = new Dictionary<string, string>();
public static void Register(string serviceName, string typeName)
{
_container.Add(serviceName, typeName);
}
The types are inserted into a static Dictionary. To register types you use the following code:
private static void InitializeContainer()
{
IocContainer.Register("SharpContainer.CustomerRepository", "SharpContainer.CustomerRepository");
IocContainer.Register("SharpContainer.IDataService", "SharpContainer.OracleDataService");
IocContainer.Register("SharpContainer.INotificationService", "SharpContainer.FaxNotificationService");
IocContainer.Register("SharpContainer.ILogService", "SharpContainer.FileLogService");
}
Now, to resolve the dependencies we will call the Resolve<T>() method which will call a private InnerResolve method.
public T Resolve<T>()
{
return (T)InnerResolve(typeof(T).FullName);
}
Here is the InnerResolve method which does the actual work:
private object InnerResolve(string typeName)
{
Type T = Type.GetType(typeName);
List<Type> registeredTypes = new List<Type>();
ConstructorInfo[] constructors =T.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (ConstructorInfo constructor in constructors)
{
ParameterInfo[] parameters = constructor.GetParameters();
foreach (ParameterInfo parameter in parameters)
{
if (!_container.ContainsKey(parameter.ParameterType.FullName))
{
break;
}
Type t = Type.GetType(_container[parameter.ParameterType.FullName]);
if (!registeredTypes.Contains(t))
{
registeredTypes.Add(t);
}
}
}
// create objects of the registered types
object[] dependentObjects = new object[registeredTypes.Count];
for (int i = 0; i < registeredTypes.Count; i++)
{
// resolve the dependent types!
dependentObjects[i] = InnerResolve(registeredTypes[i].FullName);
}
return Activator.CreateInstance(T, dependentObjects);
}
Now from the client side you can write the following code to setup up the CustomerRepository with the dependencies:
static void Main(string[] args)
{
InitializeContainer();
Customer customer = new Customer() { FirstName = "Mohammad", LastName = "Azam" };
IocContainer ioc = new IocContainer();
CustomerRepository repository = ioc.Resolve<CustomerRepository>();
repository.Save(customer);
}
As, you can see the code has greatly reduced in size because we are not manually creating the dependencies. The above code can be used for simple scenarios but if you are looking for more advanced solution then you should check out some of the open source dependency injection frameworks.
Here is a list of some of the popular DI frameworks:
Castle MicroKernel/Windsor
PicoContainer.NET
Spring.NET
StructureMap
Ninject
Unit