Writing code that is flexible, easy to maintain, and easy to test is crucial for developing contemporary apps. Dependency Injection (DI) assists us in doing just that. DI makes the code clearer and easier to maintain by allowing dependencies to be supplied from the outside rather than requiring a class to create everything it needs on its own.
In this article, we will learn:
What is Dependency Injection?
Why do we need it?
A simple example (Car and Engine)
A real-world example ( ILogger<T>
in .NET Core)
What Happens Behind the Scenes with ILogger<T>
Types of DI and Service Lifetimes
Benefits of using DI
What is Dependency Injection?
Dependency Injection (DI) is a design pattern where a class receives its dependencies from the outside , instead of creating them inside.
In simple words:
This makes code flexible, maintainable, and testable .
Without DI (Tight Coupling):
Problem: If we want to use a PetrolEngine or ElectricCar, we must
modify the Car class. This breaks the Open/Closed Principle (OCP) of
SOLID.
With Dependency Injection (Loose Coupling)
Step 1. Create an Interface
Step 2. Implement Engines
Step 3. Inject Engine into Car
Step 4. Register in Program.cs
Now, if you hit /run
, you will see:
Electric engine started. Car is running.
If tomorrow you want a petrol engine, just change the registration line:
Real-World Example: ILogger<T>
in .NET Core
One of the best practical uses of DI is logging.
ILogger<T> in .NET Core
is a built-in service that helps you write logs for your application,
like messages, warnings, or errors. You don’t have to create it
yourself—.NET Core provides it automatically using dependency injection.
The <T>
tells the logger which class it’s coming
from, so when you look at the logs, you can easily see where each
message originated. It’s a simple and clean way to keep track of what’s
happening in your app while keeping your code organized.
Here, ILogger<HomeController>
is automatically injected by the DI container.
No need to create a logger manually i.e. .NET handles it for us.
When you call /home/welcome
, you’ll see a log like this in the console:
Important: What Happens Behind the Scenes with ILogger<T>
When you access the URL /home/welcome
in your browser, ASP .NET Core creates a new instance of the HomeController
. It notices that the controller’s constructor requires an ILogger<HomeController>
. At this point, it asks the Dependency Injection (DI) container:
“Do we have a service for ILogger<HomeController>
?”
The DI container already has it registered internally, so it automatically provides an instance of ILogger<HomeController>
to the controller. When the GetWelcome()
method is called, the log message is sent to the console (or whichever logging provider is configured).
By default, in .NET 6/7/8 Web API, you’ll see something like this in your console:
This output comes directly from the code:
The key point here is that you never manually create the logger
and you don’t have to configure how the logs are written—the DI
container does it for you. Instead of the class being responsible for
creating its own dependencies, it simply asks for them in the constructor.
This is exactly what Dependency Injection is all about: your class
declares what it needs, and the framework provides it automatically.
Types of Dependency Injection
1. Constructor Injection (Most Common)
We
already followed constructor injection in example above. In this
approach, dependencies are passed into a class through its constructor.
It ensures that the dependency is available as soon as the object is
created. This is the most widely used method in .NET Core and is also
considered best practice.
2. Method Injection
Method Injection
is when a class receives a dependency only when a specific method is
called, instead of getting it through the constructor or a property. In
other words, the dependency is passed as a parameter to the method that
needs it. This is useful when the dependency is needed only occasionally
or for certain actions. However, if multiple methods need the same
dependency, you may end up passing it repeatedly, so it’s less commonly
used than constructor injection.
3. Property Injection
Property Injection
allows you to provide a class with its dependency through a public
property instead of the constructor. In other words, you first create
the object and then assign the dependency to its property. This can be
useful when the dependency is optional or only needed later. However,
you need to be careful—if the property is not set before using it, it
can cause errors, which is why this approach is used less often than
constructor injection.
Service Lifetimes in .NET Core
Service lifetimes in .NET Core determine how long a dependency object lives when it’s provided by the DI container. There are three main types:
Singleton:
A single instance is created and shared across the entire application.
For example, a configuration service that never changes can be
registered as singleton.
Scoped: A new
instance is created for each HTTP request. For example, a user session
service can be scoped so each request gets its own instance.
Transient:
A new instance is created every time the dependency is requested. For
example, a lightweight helper service that performs simple calculations
can be transient.
Choosing the right lifetime ensures
efficient resource use, predictable behavior, and cleaner code while
using dependency injection effectively.
Benefits of Dependency Injection
Makes code cleaner and easier to maintain.
Reduces tight coupling between classes.
Allows you to swap implementations without changing your code.
Makes unit testing easier by enabling mock dependencies.
Leads to organized, reusable, and testable code.
Helps build scalable and flexible applications, especially in large projects.
Conclusion
Dependency Injection (DI) is a powerful design pattern in .NET Core that helps make your applications cleaner, more maintainable, and easier to test.
By letting the framework provide the dependencies, you reduce tight
coupling, improve flexibility, and can easily swap implementations
without changing your classes. We saw how DI works with a simple Car and Engine example and also in a real-world scenario using ILogger<T>,
along with the different service lifetimes—Singleton, Scoped, and
Transient. Understanding and using DI effectively is a key step toward
writing professional, robust, and scalable .NET Core applications.
What’s Next?
In the next article, we will dive into Microservices in .NET Core.
You’ll learn how to design and implement small, independent services
that work together to form a larger application. We’ll also go through a
practical example to see how microservices communicate, how to manage
dependencies, and how DI plays a role in building scalable and
maintainable applications.
In this article, we will learn:
What is Dependency Injection?
Why do we need it?
A simple example (Car and Engine)
A real-world example ( ILogger<T>
in .NET Core)
What Happens Behind the Scenes with ILogger<T>
Types of DI and Service Lifetimes
Benefits of using DI
What is Dependency Injection?
Dependency Injection (DI) is a design pattern where a class receives its dependencies from the outside , instead of creating them inside.
In simple words:
This makes code flexible, maintainable, and testable .
Without DI (Tight Coupling):
Problem: If we want to use a PetrolEngine or ElectricCar, we must
modify the Car class. This breaks the Open/Closed Principle (OCP) of
SOLID.
With Dependency Injection (Loose Coupling)
Step 1. Create an Interface
Step 2. Implement Engines
Step 3. Inject Engine into Car
Step 4. Register in Program.cs
Now, if you hit /run
, you will see:
Electric engine started. Car is running.
If tomorrow you want a petrol engine, just change the registration line:
Real-World Example: ILogger<T>
in .NET Core
One of the best practical uses of DI is logging.
ILogger<T> in .NET Core
is a built-in service that helps you write logs for your application,
like messages, warnings, or errors. You don’t have to create it
yourself—.NET Core provides it automatically using dependency injection.
The <T>
tells the logger which class it’s coming
from, so when you look at the logs, you can easily see where each
message originated. It’s a simple and clean way to keep track of what’s
happening in your app while keeping your code organized.
Here, ILogger<HomeController>
is automatically injected by the DI container.
No need to create a logger manually i.e. .NET handles it for us.
When you call /home/welcome
, you’ll see a log like this in the console:
Important: What Happens Behind the Scenes with ILogger<T>
When you access the URL /home/welcome
in your browser, ASP .NET Core creates a new instance of the HomeController
. It notices that the controller’s constructor requires an ILogger<HomeController>
. At this point, it asks the Dependency Injection (DI) container:
“Do we have a service for ILogger<HomeController>
?”
The DI container already has it registered internally, so it automatically provides an instance of ILogger<HomeController>
to the controller. When the GetWelcome()
method is called, the log message is sent to the console (or whichever logging provider is configured).
By default, in .NET 6/7/8 Web API, you’ll see something like this in your console:
This output comes directly from the code:
The key point here is that you never manually create the logger
and you don’t have to configure how the logs are written—the DI
container does it for you. Instead of the class being responsible for
creating its own dependencies, it simply asks for them in the constructor.
This is exactly what Dependency Injection is all about: your class
declares what it needs, and the framework provides it automatically.
Types of Dependency Injection
1. Constructor Injection (Most Common)
We
already followed constructor injection in example above. In this
approach, dependencies are passed into a class through its constructor.
It ensures that the dependency is available as soon as the object is
created. This is the most widely used method in .NET Core and is also
considered best practice.
2. Method Injection
Method Injection
is when a class receives a dependency only when a specific method is
called, instead of getting it through the constructor or a property. In
other words, the dependency is passed as a parameter to the method that
needs it. This is useful when the dependency is needed only occasionally
or for certain actions. However, if multiple methods need the same
dependency, you may end up passing it repeatedly, so it’s less commonly
used than constructor injection.
3. Property Injection
Property Injection
allows you to provide a class with its dependency through a public
property instead of the constructor. In other words, you first create
the object and then assign the dependency to its property. This can be
useful when the dependency is optional or only needed later. However,
you need to be careful—if the property is not set before using it, it
can cause errors, which is why this approach is used less often than
constructor injection.
Service Lifetimes in .NET Core
Service lifetimes in .NET Core determine how long a dependency object lives when it’s provided by the DI container. There are three main types:
Singleton:
A single instance is created and shared across the entire application.
For example, a configuration service that never changes can be
registered as singleton.
Scoped: A new
instance is created for each HTTP request. For example, a user session
service can be scoped so each request gets its own instance.
Transient:
A new instance is created every time the dependency is requested. For
example, a lightweight helper service that performs simple calculations
can be transient.
Choosing the right lifetime ensures
efficient resource use, predictable behavior, and cleaner code while
using dependency injection effectively.
Benefits of Dependency Injection
Makes code cleaner and easier to maintain.
Reduces tight coupling between classes.
Allows you to swap implementations without changing your code.
Makes unit testing easier by enabling mock dependencies.
Leads to organized, reusable, and testable code.
Helps build scalable and flexible applications, especially in large projects.
Conclusion
Dependency Injection (DI) is a powerful design pattern in .NET Core that helps make your applications cleaner, more maintainable, and easier to test.
By letting the framework provide the dependencies, you reduce tight
coupling, improve flexibility, and can easily swap implementations
without changing your classes. We saw how DI works with a simple Car and Engine example and also in a real-world scenario using ILogger<T>,
along with the different service lifetimes—Singleton, Scoped, and
Transient. Understanding and using DI effectively is a key step toward
writing professional, robust, and scalable .NET Core applications.
What’s Next?
In the next article, we will dive into Microservices in .NET Core.
You’ll learn how to design and implement small, independent services
that work together to form a larger application. We’ll also go through a
practical example to see how microservices communicate, how to manage
dependencies, and how DI plays a role in building scalable and
maintainable applications.
ASP.NET Core 9.0 Hosting Recommendation
One of the most important things when choosing a good ASP.NET Core 9.0 hosting is the feature and reliability.
HostForLIFE
is the leading provider of Windows hosting and affordable ASP.NET Core, their
servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent
and the features of the web hosting plan are even greater than what many
hosting providers ask you to pay for.
At HostForLIFE.eu, customers can also experience fast ASP.NET Core
hosting. The company invested a lot of money to ensure the best and fastest
performance of the datacenters, servers, network and other facilities. Its
datacenters are equipped with the top equipments like cooling system, fire
detection, high speed Internet connection, and so on. That is why
HostForLIFEASP.NET guarantees 99.9% uptime for ASP.NET Core. And the engineers do
regular maintenance and monitoring works to assure its Orchard hosting are
security and always up.