Loading...
...
Posted by Firuza Polad on june 18, 2025

Dependency Injection

Dependency Injection (DI)- is a technique used to achieve Inversion Of Control (IOC) between classes and their dependencies. Helps to creating Loosely Coupling code, ensuring that classes are not responsible for creating their dependencies, instead they get dependencies from an external source. it make easy to change and replace dependencies without modifying classes. Dependency: is an object, a class need to perform its function. ex: If an "OrderService" needs a PaymentProcessor, then "PaymentProcessor" is a dependency.
Injection: instead of class creating its own dependencies (can lead to tight coupling), this dependencies are injected to class from outside. There are 3 types of Injection:

  • 1. Constructor Injection is most recommended. Most reliable , Immutable (no one can change the dependency after construction), enforced at compile-time. Best for .NET Core controllers and services with 1–5 dependencies
  • 2. Property Injection dependencies are set through public properties after the object is created. Useful for frameworks, plugins, or circular dependencies. Risk of null reference if property isn't set.
  • 3. Method Injection dependencies are passed as parameters to specific methods, rather than to the class itself. Useful when the dependency is only needed temporarily or occasionally. Not ideal for core dependencies needed in multiple methods

Code Example for Injection types



IOC container

Inversion of Control (IOC) is a tool that automates the implementation of Dependency Injectyion(DI). Traditionally, in a program the main application flow is controlled by program itself, create and manage its own dependencies. But in IOC, an external system like 'Container' takes over the responsibility. It automatically resolve dependency and inject them where needed. Eg: Autofac, Unity, and NInject, implement the IoC principle using DI.

DI container

Dependency Injection (DI) container is a framework that manages creation, configuration, lifetime of objects and their dependencies. It is a type of IOC. Like Microsoft.Extensions.DependencyInjection in .NET core, automatically provide instance of dependencies when class needs, instead of creating these dependencies manually. Also third-party containers like 'Autofac' offer more advanced features. We register service in program.cs so the container knows how to resolve them when needed. There are 3 main lifetime for services:

  • 1. Transient a new instance is created every time the service is requested. Good for simple logic, short-lived operations. Example: we have 2 request, each time a new object is created.
    Request 1: Controller --> New UserService (1)
    Request 2: Controller --> New UserService (2)
    Even inside the same request if we request the service multiple time, we also get new instance.
    Request 1: Controller --> New UserService (1) ; Controller --> New UserService (2)
  • 2. Scoped a new instance is created per HTTP request. All dependencies share same instance in the same request. Good for EF Core DbContext, request-specific logic. Again we have 2 Request , in the same request the same instance is reused.
    Request 1: Controller --> Same UserService
    Repository -->Same UserService
    But in new Http request a new instance is created.
    Request 2: Controller --> New UserService
  • 3. Singleton a single instance is created and shared throughout the lifetime of application. Good for configuration, caching, logging. Again we have 2 Request , in the same and different requests the same instance are reused
    Request 1: Controller --> Same UserService
    Request 2: Controller --> Same UserService

AutoFac

Is a DI container, but more flexible and advanced compared to default built-in DI. Offer more configuration options, such as controlling how long object live, register services in more detailed way. Supports constructor, property, and method injection, and provides module-based registration. To use AutoFac we install Autofac.Extensions.DependencyInjection package.

RegisterType vs RegisterInstance:
a. RegisterType(): in here we give a class type to Autofac, and it create new instance whenever it's needed. We can change the lifetime using some methods like .SingleInstance() , .InstancePerLifetimeScope() . It is most common and flexible for services. eg: in below line, every time Autofac resolves IUserProcessor, it create new instance of UserProcessor builder.RegisterType<UserProcessor>().As<IUserProcessor>();
b. RegisterInstance(obj) : in here we create an object ourselves, Autofac uses that same instance every time when Interface is requested, act like Singeleton (no new instance). If we already have an object like config,logger it is useful. eg: in below line Autofac will always return same logger object whenever Ilogger is requested. var logger = new Logger(); builder.Services.RegisterInstance(logger).As<ILogger> ();

NOTE: Let's say we have UserService class and it has dependency on IUserProcessor , Autofac will check UserService's constructor and sees it needs IUserProcessor, so it'll look up for IUserProcessor => bound to UserProcessor, and automatically inject an instance of UserProcessor.

Code Example for Container



SOLID principles

Are designed to help move from tightly coupled to loosely coupled code, increases cohesion between classes and reduces unnecessary coupling between them. Resulting in code is more modular and easier to extend, help to reduce number of bugs in code, making easier find and solve bugs. Solid has 5 principles, but first let see Coupling and Cohesion :
a. Coupling means- dependency between components (Classes). Goal is, classes should depend on abstractions, not concrete implementation.
Low Coupling means - Classes are independent each other, changes in one class affect others.
High coupling means- Classes are related each other, changes in one class may break/require changes in others.
b. Cohesion means- how related members of class. Goal is, each class should have one clear responsibility.
Low cohesion means- member of class are unrelated with each other
High cohesion means- member of class are closely related with each other and used for single
purpose.
Summary: SOLID, helps to design systems where classes interact with each other through interfaces or abstractions (low coupling) and members in a class are closely related and used for single purpose (high cohesion).

Code Example for SOLID


Single Responsibility Principle (SRP): A class only have 1 reason to change, it means it should have only 1 responsibility, eg: Suppose we have 'User' class, that store user information and handles saving user data to DB. In here User has 2 responsibilities:
a. It must manage user information
b. Save information to DB. And if DB logic changes, then we have to modify User class, which Violating SRP.


Open Closed Principle (OCP): Entites (classes, modules, functions) should be open for extension but closed for modification, which means we should be able to add new behavior without modifying existing code, eg: Suppose we have 'Payment' class that handle different payment methods, if we need to add new payment method, we must update Payment class, and it violates OCP.


Liskov Substiton Principle (LSP): Object of base class should be replaceable with objects of its child class without breaking application, eg: Suppose we have 'Bird' base class, and Sparrow , Penguin are subclasses. When we use Bird base class, then any subclass should act like Bird without breaking the system. In this example when we call Fly() it will throw exception because Penguin can't fly, but we are assuming all Bird types can fly, so Penguin will change behavior of base class, which violated LSP. When we split behaviors appropriately like not all birds can fly, then we don't force them to implement Fly().


Interface Segregation Principle (ISP): Client class shouldn't be forced to implement an Interface they don't use, means don't create large Interface, instead of split them into smaller ones, eg: if a class need only to implement ReadFile() method not WriteFile() one, it forced to implement both, which violates ISP.


Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules, both should depend on abstractions, means abstraction should not depend on details, instead details should depend on abstraction, eg: Suppose we have User (high-level) and DatabaseLogger (low-level) classes, if we want to switch DatabaseLogger to FileLogger then we must change User class, resulting is tightly coupled. Violates DIP.
Dependency Inversion Principle (DIP) vs Dependency Injection (DI) : both of them reduce tight coupling between components, but they do it different ways. DIP is one of 5 SOLID principle of OOP. DI is design pattern that define how object get their dependencies. DI is used to implement DIP.
Without DIP and DI: Violates DIP: Depends on a concrete class; No DI: Dependency is created inside the class; Hard to test or replace DatabaseLogger With DIP and DI: Depends on interface; Injects logger from outside; Easy to test, extend, or swap implementation


Information
All diagrams, Folder structure, Code example in this blog were created by me using Lucid , Canva and IDE If you'd like to contribute, request more diagrams or text, or suggest improvements, feel free to contact me
© 2025 Firuza Polad. All rights reserved.

Let’s Connect

I’m open to collaboration, remote and freelance work!