In the previous post we implemented configuration in a .net core console application. In this post let us look at implementing dependency injection in a .net core console application.
An ELI5 explanation of dependency injection is provided in this historically significant stackoverflow post. DI helps implement a key design pattern called loose coupling. DI enables loose coupling by allowing us to program against an interface(contract), rather than a concrete implementation. This makes code more maintainable and testable.
Dependency injection can be implemented using an off the shelf DI container or by custom coding a Pure DI implementation. A pure DI implementation will need to provide for Object composition, Lifetime management and interception. A pure DI implementation is not a trivial exercise. The time and effort required to create one for a specific solution is not justified.
A DI Container is a software library that provides DI functionality and automates many of the tasks involved in Object Composition, Interception, and Lifetime Management. It’s an engine that resolves and manages object graphs. These DI Containers depend on the static information compiled into all classes. Using reflection, they can analyze the requested class and figure out which Dependencies are needed. Many excellent DI Containers are available for the .NET platform such as Autofac, StructureMap, Ninject, and also the built-in Microsoft.Extensions.DependencyInjection library.This post is not a deep dive into dependency injection , but rather about using a dependency injection framework to implement DI in a .net core console application using the Microsoft dependency injection service The code sample for this post is here.
We start off by creating a console application. To use the Microsoft Dependency injection library we need to install the Microsoft.Extensions.DependencyInjection nuget library. We need a service container and this is provided by the ServiceCollection class. We create an instance of the ServiceCollection class and add services to this class with a specific service lifetime scope as below . I register the Customer type as a concrete implementation of the ICustomer interface. I also directly register a Type ( ConsoleApplication). By doing this i am trying to use the program class as a shell which creates the service container and registers the necessary services.
The service collection needs to be disposed on exit and that is performed in the DisposeServices method.
The programs entry point now calls RegisterServices to register all the necessary services using configuration as code . Once this is done it creates an object lifetime scope using the serviceprovider.CreateScope call. It then calls the GetRequiredService generic method to get the concrete ConsoleApplication type and calls the run method on it. The ConsoleApplication is created by automatically injecting its dependencies through constructor injection. In this case the ConsoleApplication class depends on the ICustomer interface. The dependency injection system intercepts this call and passes in the concrete implementation of ICustomer which was configured in RegisterServices.
We can also change our code to auto register types using assembly scanning. To implement assembly scanning we can use the .net core reflection api’s and LINQ as below.
We now have a fully functioning DI system in a .net core console application. we can now design our interfaces and classes and make sure that they are designed to be maintainable and independently testable. We can create mocks and use them in place of the actual implementation etc.