What is dependency injection and why is it important? According to wikipedia is a technique whereby one object supplies the dependencies of another object. This concept may be confusing for some and for others that are in their beginnings in programming it may seem a bit overwhelming or perhaps not necessary.
To understand why DI was conceived in the first place we have to take a look at another term, tight – coupling. Tight coupling in software programming is a scenario where the classes are highly dependent on one another. This may not seem a problem for some, but let’s consider the following scenario:
static void Main(string[] args) public class ClassOne { } public class ClassTwo { private readonly ClassOne _classOne; public ClassTwo(ClassOne classOne) { _classOne = classOne; } } public class ClassThree { private readonly ClassTwo _classTwo; private readonly ClassOne _classOne; public ClassThree(ClassOne classOne, ClassTwo classTwo) { _classTwo = classTwo; _classOne = classOne; } }
In the above example you can see the basic definition of tight coupling. Where classes depend on on another. For small size applications this may not seem to be a problem, but what happens for larger applications where changes can occur and object shapes and dependencies may vary?
Dependency Injection works to solve this issue, but DI doesn’t have the purpose to instantiate any type of class. Imagine that you have a class Line which has two arguments (Point start, Point end). It would be absurd to think that DI should instantiate a line, it’s arguments are dynamic and do not represent a fixed state.
The only purpose of a DI module is handle and inject Services in our modules.
In this article I will show you how to :
-
Create the Service Provider
-
Leverage the ActivatorUtilities to instantiate non-service objects
-
The Object Factory
There are many DI frameworks available on the marked, but with .NET Core we have a built in framework which is quite powerfull and with the use of the built-in helpers we can achive many things.
The DI mechanism in .NET Core exposes two main classes with which we will built the engine. The ServiceCollection class which it’s used to register the services and from which the ServiceProvider class is used. The Service Provider class is the one responsible for “acquiring” the required services.
These classes are available under the nuget package “Microsoft.Extensions.DependencyInjection”
A. Creating the service provider.
Let’s consider the following services:
public class EmpoyeesService : IEmployeesService { private readonly List _employees = new List { new Employee("John", EmployeeGrade.I), new Employee("Jack", EmployeeGrade.II) }; public List FindEmployees(Expression<Func<Employee, bool>> where) { return _employees.Where(where.Compile()).ToList(); } } public class CompensationService : ICompensationService { public double GetCompensationFor(EmployeeGrade employeeGrade) { switch (employeeGrade) { case EmployeeGrade.I: return 100; case EmployeeGrade.II: return 1000; case EmployeeGrade.III: return double.MaxValue; default: return 0; } } } public class PaymentsService : IPaymentsService { private readonly IEmployeesService _employeesSvc; private readonly ICompensationService _compensationService; public PaymentsService(IEmployeesService empoyeeRepository, ICompensationService compensationService) { _employeesSvc = empoyeeRepository; _compensationService = compensationService; } public double CalculatePaymentsForEmployeesWithFirstGrade() { //find employees List employees = _employeesSvc.FindEmployees(x => x.Grade == EmployeeGrade.I); //get compensation var compensation = _compensationService.GetCompensationFor(EmployeeGrade.I); return employees.Count() * compensation; } }
The next step is building the DI mechanism. It’s as simple as adding the services in the service collection and building the provider
var collection = new ServiceCollection(); collection.AddScoped<IEmployeesService, EmpoyeesService>(); collection.AddScoped<ICompensationService, CompensationService>(); collection.AddScoped<IPaymentsService, PaymentsService>(); // ServiceProvider serviceProvider = collection.BuildServiceProvider(); return serviceProvider;
And that’s it. Now we can get any service simply by demanding the service from the provider, and any dependencies will be solved by the ServiceProvider
IPaymentsService paymentsSvc = serviceProvider.GetService(); Console.WriteLine($"Financial effort : {paymentsSvc.CalculatePaymentsForEmployeesWithFirstGrade()}");
B. ActivatorUtilities
Until now we have registered some services and we have seen how simple is to get one service using the provider. Note that all of the services have been registered and that’s key when thinking about getting an object.
What about an instance of an object that is not registered?
Consider the following:
- A class that only requires services as parameters
- A class that also requires other parameters then a service
public class TheBoss { private readonly IPaymentsService _paymentsService; public TheBoss(IPaymentsService paymentsService) { _paymentsService = paymentsService; } public void MakePayments() { Console.WriteLine($"Pay :{_paymentsService.CalculatePaymentsForEmployeesWithFirstGrade()}"); } } public class HREmployee { private readonly string _firstName; private readonly IEmployeesService _employeesService; public HREmployee(string employeeName, IEmployeesService employeesService) { _firstName = employeeName; _employeesService = employeesService; } }
.NET Core actually provides a powerfull tool which it also uses in some frameworks (like ASP) to solve the controllers.
This is called ActivatorUtilities and it creates objects using the service provider as a source of services.
Let’s see now how to use it
ObjectFactory HrFactory = ActivatorUtilities.CreateFactory(typeof(HREmployee), new Type[] { typeof(string)}); HREmployee natashaFromHr = HrFactory.Invoke(serviceProvider, new object[] { "Natasha" }) as HREmployee;;
C. The Object Factory
The object factory is actually a Factory Pattern that is fixed for one type only. According to the documentation on the microsoft’s page the method does “Create a delegate that will instantiate a type with constructor arguments provided directly and/or from an “.
In essence it encapsulates the type of the object to be created as well as other service dependencies it might need.