Getting Started On Dependency Injection In .NET Applications

Getting Started On Dependency Injection In .NET Applications

SOLID principles are core software design principles, intended to make software design more understandable and maintainable. Every letter stands for a concept and below is the list of five concepts:

  • Single Responsibility, there should never be more than one reason for a class to be changed
  • Open-Closed Principle, the class should be open for extension but closed for modifications
  • Liskov Substitution Principle, meaning any pointer to a base class should also be able to use the objects of derived classes, without knowing it
  • Interface Segregation Principle, multiple client specific interfaces are always better than one big fat interface for a class
  • Dependency Inversion, meaning rely on abstractions instead of relying on the concrete implementations

In this article, we are going to have a closer look at what is dependency inversion and how it can be used in .NET applications.

Scenario

When we write code for any application, usually it might involve multiple classes (Single Responsibility Principle from SOLID). So, let’s say we have a

  • Controller class, which is dependent on a BusinessLogic class
  • BusinessLogic class is dependent on Repository class
  • Repository class might be dependent on some classes from .NET

We will use this scenario to discuss the next two concepts.

Dependency Tree

Most of the real world applications are written in this way, the direction of compile time dependencies flows in the direction of runtime execution. So, these dependencies can be plotted as a tree as shown below. This tree is called as dependency tree.

Dependency Inversion - Direct Dependency Tree
Dependency Inversion – Direct Dependency Tree

What is the issue ?

So, what is the real issue in above example ? The issue is, the consumer class need to know about all dependencies at compile time.

Why is this an issue?

If there is only one developer working for the application, the issue might never surface. But in case of real world application this is never the case.

Now a days, there is more focus on making the application cloud native and making the application modular, where each module can be fairly independently deployed. In such setup, there might be multiple teams working on different modules.

The compile time dependency on the concrete implementation can affect the development velocity as teams cannot continue in parallel.

The compile time dependency may also result into lots of issues just for maintaining the application. Let’s say a class was created to support external identity provider, let’s say, google, for authentication. Now, if you want to change the external identity provider, to let’s say facebook, then it might be very expensive change, if the assumptions made in the original class design, are valid or not.

Also, what if we want to unit test the consumer class ? How would we make the consumer class use the stub in place of actual dependency ?

Inverted Dependency Tree

Now for the same scenario. But instead of directly adding reference to a concrete implementation, let’s say an interface is extracted. So

  • CoreLogic class, which is dependent on an interface IRepository class to get the data from database, and IFileLogger class for logging. These interfaces document all the assumptions between consumed classes and the consumer class and these interfaces are controlled mostly by the consumers.
  • FileLogger class might be dependent on some class (or classes) from .NET and it implements the IFileLogger interface.
  • Repository class might be dependent on some classes from .NET, and it also implements the IRepository interface

So, now CoreLogic class would control the interfaces (because of interface segregation principle stated above).

And Repository and FileLogger will have to make sure they follow the same interface. Thus, the name, dependency inversion.

So, the compile time dependency tree would change as shown below. But run-time dependency would be still the same. But because of interfaces, we have now additional advantage of replacing the concrete implementation without having to do any code changes in consumer classes.

Dependency Inversion - Inverted Dependency Tree
Dependency Inversion – Inverted Dependency Tree

Why Inverted Tree is Better ?

Below are some of the high level reasons

  • To decouple the execution from implementation. In above example, controller class will just use interface without knowing the concrete implementation, which can be resolved at run time.
  • To free the classes and modules from assumptions. The assumptions are now in the interface.
  • To make the code more maintainable,
  • To prevent the side-effects while replacing the module. Replacing module is very easy as long as same interface is being used by the new replacement.

Dependency – Inversion vs Injection

Dependency Inversion is a design principle. It is a guideline for decoupling modules from one another. The actual implementation of that principle might vary.

There are different implementations which are in use today. Some of them include:

Thus, Dependency Inversion and Dependency Injection are not same things. Dependency inversion is a principle and Dependency injection is a way in which the dependency inversion principle is implemented.

Dependency inversion is also known as inversion of control (IoC) pattern.

Inversion of control is sometimes facetiously referred to as the “Hollywood Principle: Don’t call us, we’ll call you”.

Wikipedia

How to apply in .NET apps ?

Dependency injection is the first class citizen in .NET.

Below is the stepwise process for using dependency injection:

  • Create interface (or base class) as an abstraction
  • Create the concret implementation
  • Registration of the dependency in a service container. The .NET provides a built-in service container, IServiceProvider.
  • Services are typically registered at the app’s start-up, and appended to an IServiceCollection. Once all services are added, you use BuildServiceProvider to create the service container
  • Inject the dependencies via constructor. Framework takes care of creating object and destroying them when they are not needed anymore.

The lifetimes Scoped, Transient and Singleton are supported. If it is a .NET Core web application, it can be very easy to register the dependencies. We just need to call AddScoped, AddTransient, AddSingleton methods in ConfigureServices method of Startup.

Below example shows the usage.

public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ICustomLogger, CustomLogger>();
services.AddScoped<IRepository, Repository>();
services.AddControllersWithViews();
}
}
view raw Startup.cs hosted with ❤ by GitHub

I hope this post was useful. Let me know your thoughts.

Leave a Reply

This Post Has One Comment