Dependency Injection in Net Core: What, Why, How-to, Which (Explained with cats)

Gerardo León
7 min readJan 25, 2021

What is it? Why to use it? How to do it? Which one to use?

DI this, DI that, but no one explains us the magic of Dependency Injection. So, let’s grab DI from its bare principles and learn how the magic trick of having an already instantiated service class at the door of your constructor is done.

First, let’s catch up with some words we will or already have come across many times while searching for DI.

Vocabulary needed for understanding Dependency Injection
Let’s PPRRReview some vocabulary

Vocabulary 📚

DI: Dependency Injection (the thing, concrete implementation)

IoC: Inversion of Control (the idea, abstract concept)

Contract: Agreement between two or more parties that specifies actions to do

Dependency: A service used by another service or, generically, class

DIY (Dependency Inject Yourself) 💉

So, what really is Dependency Injection (DI)? it’s the implementation of the Inversion of Control (IoC) concept, and what is Inversion of Control? Letting the program itself initialize classes (services) used by other classes (consumers, calling components) in the runtime, so to speak, take out the worry of initializing a service in a component and let it mind its own business.

Dependecy Injection is like a store clerk ready to help out finding what you need, instead of you looking for everything
The magic of DI (self-made)

So, on which principles do these sustain? SOLID, but do not worry, the D of Dependency Inversion has already been explained, so let’s move on to the other two main ones.

The S and I in SOLID 🤓

Single Responsibility Principle (SRP)

When you hear SR Principle, you gotta think of a class as a person. Sure thing, it can do lots of stuff, but can just specialize in only one. So, you cannot bloat it with taking responsibilities that do not correspond to its one-and-only job and respect that. Take for example: If its job is to be a mechanic, then why to make him/her responsible of making its tools when a manufacturer can make it?

A job with many titles describes many responsabilities, that’s the same as a bloated class
This is a monolithic bloated class

Interface Segregation Principle (ISP)

Code to an interface, you might have already heard about it and the reason is that interfaces give you two great benefits:

  1. Abstract your thinking, a cat and a dog are common in the sense of both being spoilable, though how you spoil each is different.
  2. Ensure behavior compliance as a contract, implementing from an interface makes sure classes describe shared behavior of actions or characteristics, though unique in their implementations.

So, if you think of a spoilable animal, you think of a cat or a dog, but how you spoil each is quite different. See:

Petting map of a dog and a cat, both are spoilable, but each is spoiled in a different manner
A cat and a dog cannot be spoiled the same way, though you can spoil both

Implementation 💻 🐱‍💻

Enough theory for today, let’s see an example written in C# Net Core:

First create an API project in Net Core with C#

Create an API project

We’re using a controller and two services to apply what we learned about Dependency Injection.

For this example, we’re implementing a calculator controller, one service will be responsible for making a sum and a subctraction, while the other service will be in charge of printing the result in console.

Usually, your code should look like this:

Sure, the code above works, but here comes a big “but”, it violates the principles we studied at the beginning of this article: First, Controller is initializing stuff it should not be worried about, its one job is to handle user’s request and return a response; Second, there’s no interface that ensures that a class is enforced to comply with its implementation, that is like a party expected to do something by a contract clause. Just adding to the mistakes stack, we’re not using Dependency Injection at all.

So, let’s fix all of these and set a file structure for our project, you can copy this:

You can see that under Services/ there are a pair of classes that are preceded by an “I”, it stands for interfaces, Yes! the ones we talked about just some minutes ago, let’s see what they contain:

Interface cat (ICat, cat dress as a business man with some bucks) means business
Interface cat (ICat) means business

So, these interfaces are like contracts. Now that we have them ready, let’s see who are the other parties signing these “contracts” or, in other words, who will be compliant to these:

If you’re a sharp observer, you might have already noticed that our compliant classes names (implementations) are followed by the names of our “contracts” (interfaces). You guessed right: they’re implementing these interfaces and notice too that interface and implementation share methods, that is because implementation is expected to declare and define them. Try changing its name from the implementation class (PrinterService or CalculatorService) and you’ll see that a <<Not implementing interface>> error pops up.

Up until now, we have set up our interfaces and services. Now, let’s see how they show up in the controller:

I trust you’re a sharp observer and notice that we’re calling services interfaces and not the services implemented themselves, this brings one reason and one benefit:

Reason -> not to mess up with internal implementation logic, as we can see, interfaces do not implement variables or logic, they just declare stuff that has to be implemented by another class;

Benefit -> we do not have to worry about messing up with implementation logic such as public variables and the like.

Cat says stop

But hey hey! Wait, don’t run and execute the application, yes, we injected services into constructors, though the injection is empty. And here lies the magic trick of Dependency Injection: Net Core has its own native DI Container. What is a DI Container? Let’s agree on its definition as a piece of code in charge of initializing stuff we don’t want other classes to worry about initializing.

Let’s go to the Startup.cs, especifically to the ConfigureServices method:

Look! Here we especify that our implementations are accesible through their interfaces.

Now that’s ready to test it, I’m gonna use Insomnia API Client to test this, but feel free Postman or other API Clients of your like:

Query as http://localhost:5001/calculator/sum?a=1&b=2

And check the console, it should have brought up something like this:

DI Container accessed the PrinterService constructor, which means it was instantiated without CalculatorController worrying about it, and also, CalculatorController managed to get the result sum from CalculatorService without having to initialize anything: All was thanks to the DI Container in Startup.cs.

Singleton? Scoped? Transient? Which one? 🤔🤔

So far… so good… you’re ready to move on with life now that you know what Dependency Injection is, Why to use it, and How to implement it, but there’s a question remaining, Which one should you use? And when I say which I mean which one of these three types of service initialization:

Singleton 😎: initializes your service as a singleton, so it’s static (notice: as a static class, means no other variables than static ones are allowed, so do not mix it with services that are not singleton)

Scoped 😄: initializes given service once per request, that is, its lifetime starts on its first call through a constructor and ends when user request is resolved.

Transient 🙂: initializes given service every time it’s called through a constructor, meaning that, in terms of perfomance, this is the one consuming the most resources.

Let’s better understand these concepts with examples:

Modify the IPrinter.cs and PrinterService.cs to have a PingPong method that we will see in the console:

The PingPong method will let us know an instance has been activated, now let’s call it in the Controller and CalculatorService. See that according to what we learned, the PrinterService should not be in CalculatorService, but for testing purposes we will do it:

Now, in our Startup.cs, the AddSingleton is already implemented, so let’s see what happens, execute the query twice:

As you see, PrinterService was instantiated only once and it’s the same in every call, just as a Singleton would behave.

Now, let’s replace AddSingleton with AddScoped:

Execute the query twice again:

Notice PrinterService was instantiated twice, one per request, just as a Scoped instance should do.

Finally, modify code to make it Transient:

Let’s predict its behavior: if we run the query once, PrinterService should instantiate twice since it is called in Controller and CalculatorService, so that if we query twice, that means fours PrinterService instances. Let’s see:

We were right! 🎉🎉

This article doesn’t intend to tell you When you should use each type of instantiation, that would depend on requirements. Though I want to emphasize once more how useful and easy to learn Dependency Injection can be.

Next time, I’ll bring another option for DI Containers: Autofac DI Container.

--

--

Gerardo León

Lazy backend dev, music nerd, flamboyant classmate