Design Patterns & Practices  

Dependency Injection in .NET – Explained in Simple & Practical Way (With Examples)

Introduction

Dependency Injection (DI) is one of the most important concepts in modern .NET development, especially when building scalable applications like ASP.NET Core APIs, Microservices, or Enterprise applications.

What is Dependency Injection?

Dependency Injection is a design pattern in which an object receives its dependencies from outside rather than creating them itself.

In simple words:

  • ❌ Without DI → Class creates its own dependency

  • ✅ With DI → Dependency is provided to the class

Why Do We Need Dependency Injection?

Let’s understand with a real-life example.

Imagine:

A Car needs an Engine.

❌ Without DI

public class Car
{
    private Engine _engine;

    public Car()
    {
        _engine = new Engine();  // Tight coupling
    }

    public void Start()
    {
        _engine.Run();
    }
}

Problems:

  • Tight coupling

  • Hard to test

  • Not flexible

  • Difficult to replace Engine

✅ With Dependency Injection

public class Car
{
    private readonly IEngine _engine;

    public Car(IEngine engine)
    {
        _engine = engine;
    }

    public void Start()
    {
        _engine.Run();
    }
}

Now the Engine is injected from outside.

This makes:

  • Code loosely coupled

  • Easy to test

  • Easy to replace implementation

Important Terms

1️⃣ Dependency

A class that another class needs.

Example:
Car depends on Engine.

2️⃣ Inversion of Control (IoC)

Instead of the class controlling dependencies, control is given to the container.

3️⃣ IoC Container

Framework component that manages dependencies.

In .NET → Built-in DI container

Types of Dependency Injection

1️⃣ Constructor Injection (Most Common)

Dependency is passed through the constructor.

public class UserService
{
    private readonly IEmailService _emailService;

    public UserService(IEmailService emailService)
    {
        _emailService = emailService;
    }
}

✔ Recommended approach

2️⃣ Property Injection

public IEmailService EmailService { get; set; }

Less used in .NET Core.

3️⃣ Method Injection

public void SendEmail(IEmailService emailService)
{
    emailService.Send();
}

Dependency Injection in ASP.NET Core (Real Example)

Let’s create a simple example.

Step 1: Create Interface

public interface IMessageService
{
    string GetMessage();
}

Step 2: Create Implementation

public class MessageService : IMessageService
{
    public string GetMessage()
    {
        return "Hello from Dependency Injection!";
    }
}

Step 3: Register Service in Program.cs

builder.Services.AddScoped<IMessageService, MessageService>();

Step 4: Inject in Controller

public class HomeController : Controller
{
    private readonly IMessageService _messageService;

    public HomeController(IMessageService messageService)
    {
        _messageService = messageService;
    }

    public IActionResult Index()
    {
        var message = _messageService.GetMessage();
        return Content(message);
    }
}

Now .NET automatically injects MessageService into HomeController.

Service Lifetimes in .NET

When registering services, you must define the lifetime.

1️⃣ Transient

services.AddTransient<IService, Service>();
  • New instance every time

  • Good for lightweight services

2️⃣ Scoped

services.AddScoped<IService, Service>();
  • One instance per HTTP request

  • Most commonly used in web apps

3️⃣ Singleton

services.AddSingleton<IService, Service>();
  • One instance for the entire application lifetime

  • Good for caching and configuration

Why DI is Important for Testing?

Without DI: You cannot mock dependencies.

With DI: You can easily use Moq or fake services.

Example:

var mockService = new Mock<IMessageService>();
mockService.Setup(x => x.GetMessage()).Returns("Test");

var controller = new HomeController(mockService.Object);

Now testing becomes easy.

Real-World Usage in Microservices

In microservices, DI is used for:

  • Repository pattern

  • Database context

  • Logging

  • API services

  • External services

  • Authentication services

Without DI → Microservices architecture becomes messy.

Common Mistakes

  • ❌ Injecting too many services in one class

  • ❌ Using Singleton for DbContext

  • ❌ Not using interfaces

  • ❌ Creating services manually with the new keyword

Best Practices

  • ✔ Use Constructor Injection

  • ✔ Use Interfaces

  • ✔ Keep services small

  • ✔ Use Scoped for DbContext

  • ✔ Avoid service locator pattern

Real World Example Structure

  • Controllers

  • Services

  • Repositories

  • Interfaces

  • Models

  • Program.cs

Register all services in one place.

Benefits of Dependency Injection

  • Loose Coupling

  • Easy Unit Testing

  • Better Maintainability

  • Flexible Architecture

  • Scalable Applications

Final Summary

Dependency Injection is not just a feature—it is a foundation of modern .NET architecture.

If you are building:

  • APIs

  • Microservices

  • Enterprise Applications

  • Clean Architecture Projects

Then DI is mandatory.

Summary

Dependency Injection is a core design pattern in modern .NET development that promotes loose coupling, testability, maintainability, and scalability. By using constructor injection, proper service lifetimes, and the built-in .NET DI container, developers can build clean, modular, and enterprise-ready applications while avoiding tight coupling and architectural complexity.