Introduction to Dependency Injection in C# for Beginners

What is Dependency Injection?

Dependency Injection (DI) is a design pattern used in C# and other object-oriented programming languages to achieve loose coupling and improve testability, maintainability, and flexibility in applications. It is widely used in ASP.NET Core applications.

At its core, DI allows us to inject dependencies (such as services, repositories, or configurations) into a class instead of hardcoding them. This helps to separate concerns and makes code easier to manage.

Why is Dependency Injection Important?

1.Reduces Coupling

  • Without DI, classes are tightly coupled, meaning changes in one class may require changes in multiple places.
  • DI promotes loose coupling, making it easier to modify and extend the application.

2.Improves Testability

  • Without DI, testing requires real database connections or APIs.
  • With DI, we can inject mock dependencies, making unit testing more efficient.

3.Enhances Maintainability & Readability

  • When dependencies are injected, classes become cleaner and focus only on their main responsibility.
  • It simplifies code maintenance and debugging.

🔹 How Does Dependency Injection Work?

Dependency Injection works by injecting dependencies into a class rather than creating them inside the class. It follows three main principles:

1️. Constructor Injection (Most Common)

Dependencies are passed through a class constructor.

2️. Property Injection

Dependencies are set using public properties.

3️. Method Injection

Dependencies are passed as method parameters.

Example: Without Dependency Injection (Tightly Coupled Code)

Let's say we have a service that sends notifications to users.

public class NotificationService
{
    private readonly EmailService _emailService;

    public NotificationService()
    {
        _emailService = new EmailService();  //  Bad Practice: Tightly Coupled
    }

    public void SendNotification(string message)
    {
        _emailService.SendEmail(message);
    }
}

public class EmailService
{
    public void SendEmail(string message)
    {
        Console.WriteLine($"Email sent: {message}");
    }
}

Problems With This Approach

  • Tightly Coupled: NotificationService is directly dependent on EmailService.
  • Difficult to Test: If we want to test NotificationService, we cannot replace EmailService with a mock.
  • Not Flexible: If we need to send SMS instead of email, we must modify NotificationService.

Example: With Dependency Injection (Loosely Coupled Code)

Now, let's refactor the code using Dependency Injection.

Step 1: Define an Interface

We define an interface for sending notifications.

public interface INotificationSender

   void Send(string message);

Step 2: Implement the Interface

We create an EmailService that implements INotificationSender.

public class EmailService : INotificationSender
{
    public void Send(string message)
    {
        Console.WriteLine($"Email sent: {message}");
    }
}

Step 3: Inject the Dependency

We inject INotificationSender instead of directly creating an instance of EmailService.

We inject INotificationSender instead of directly creating an instance of EmailService.

public class NotificationService
{
    private readonly INotificationSender _notificationSender;

    // Constructor Injection
    public NotificationService(INotificationSender notificationSender)
    {
        _notificationSender = notificationSender;
    }

    public void SendNotification(string message)
    {
        _notificationSender.Send(message);
    }
}

Step 4: Register Dependencies in ASP.NET Core

Now, we register our services using the built-in Dependency Injection container in Program.cs.

// Register EmailService as INotificationSender
services.AddScoped<INotificationSender, EmailService>();
services.AddScoped<NotificationService>();

Key Benefits of Using Dependency Injection in C#

Injecting dependencies into controllers.

[ApiController]
[Route("api/[controller]")]
public class NotificationsController : ControllerBase
{
    private readonly NotificationService _notificationService;

    public NotificationsController(NotificationService notificationService)
    {
        _notificationService = notificationService;
    }

    [HttpPost]
    public IActionResult Send([FromBody] string message)
   {
        _notificationService.SendNotification(message);
        return Ok("Notification sent successfully!");
    }
}

Now, ASP.NET Core automatically resolves dependencies for NotificationService.

Conclusion

Dependency Injection is a powerful technique that improves code quality, testability, and maintainability.

Without DI → Classes are tightly coupled, making them hard to test and modify.

With DI → We can inject dependencies flexibly, making our code loosely coupled and scalable.

Download source code

C# dependency injection