What is Dependency Injection?

Lets starts Dependency Injection with this real life example that we have to create a user service which send email when users register, the code would look like:

public class UserService
{
    private readonly EmailService _emailService;

    public UserService()
    {
        _emailService = new EmailService();
    }

    public void RegisterUser(string email)
    {
        // Save user to database

        _emailService.SendWelcomeEmail(email);
    }
}

At first glance, this code seems perfectly fine. The UserService creates an instance of EmailService and uses it to send a welcome email.

However, this approach introduces a significant problem.

The UserService is tightly coupled to EmailSerivce. So here the class UserService is responsible not only to register users but also it needs to creates its own dependency which is EmailService. Suppose initially EmailService implemented SMTP to send emails but later you decide to use SendGrid, you will have to modify UserService class also.

This violates one of the most important principles of software design: classes should focus on their own responsibilities rather than managing the creation of other objects.

This is where Dependency Injection (DI) comes in.

Dependency Injection Defined

Dependency Injection is a design pattern that allows a class to receive the objects it depends on from an external source instead of creating them itself.

Instead of constructing dependencies using the new keyword, they are injected into the class, usually through its constructor.

Let’s rewrite the previous example using Dependency Injection.

public class UserService
{
    private readonly IEmailService _emailService;

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

    public void RegisterUser(string email)
    {
        // Save user to database

        _emailService.SendWelcomeEmail(email);
    }
}

Notice that the UserService no longer creates an EmailService. Instead, it simply declares that it needs an object implementing IEmailService.

Someone else is responsible for providing that dependency.

This simple change makes a huge difference.

Understanding Dependencies

A dependency is simply another object that a class needs to perform its work.

Consider this example:

public class OrderService
{
    private readonly IOrderRepository _repository;
    private readonly IEmailService _emailService;
    private readonly ILogger<OrderService> _logger;

    public OrderService(
        IOrderRepository repository,
        IEmailService emailService,
        ILogger<OrderService> logger)
    {
        _repository = repository;
        _emailService = emailService;
        _logger = logger;
    }

    public void PlaceOrder(Order order)
    {
        _repository.Save(order);

        _emailService.SendOrderConfirmation(order.CustomerEmail);

        _logger.LogInformation("Order placed successfully.");
    }
}

The OrderService depends on three different services:

  • IOrderRepository for storing orders.
  • IEmailService for sending confirmation emails.
  • ILogger for recording application logs.

Rather than creating these objects itself, it receives them through its constructor.

Before vs After Dependency Injection

Without Dependency Injection

OrderService
    │
    ├── new OrderRepository()
    ├── new EmailService()
    └── new Logger()

In this design:

  • The class creates all of its dependencies.
  • Replacing implementations requires modifying the class.
  • Unit testing becomes difficult.
  • The class is tightly coupled to specific implementations.

With Dependency Injection

OrderService
       ▲
       │
+-----------------------------+
| ASP.NET Core DI Container   |
+-----------------------------+
       │
       ├── OrderRepository
       ├── EmailService
       └── Logger

Now the OrderService simply requests the services it needs, while the ASP.NET Core Dependency Injection container creates and supplies the appropriate implementations.

Service Lifetimes

When registering services, you specify their lifetime.

There are three lifetimes.

1. Transient

builder.Services.AddTransient<IEmailService, EmailService>();

A new object is created every time it’s requested.

Best for:

  • Lightweight services
  • Stateless services

2. Scoped

builder.Services.AddScoped<IOrderService, OrderService>();

One instance per HTTP request.

Best for:

  • Business services
  • Repository pattern
  • Entity Framework DbContext

Scoped is the most commonly used lifetime.


3. Singleton

builder.Services.AddSingleton<ICacheService, CacheService>();

Only one instance exists for the application’s lifetime.

Best for:

  • Configuration
  • Logging
  • Caching
  • Shared services

Avoid storing request-specific data in singleton services.

    Benefits of Dependency Injection

    Using Dependency Injection provides several important advantages:

    1. Loose Coupling

    Classes depend on abstractions (interfaces) rather than concrete implementations, making it easier to replace or extend functionality.

    2. Easier Testing

    Mock implementations can be injected during unit testing without modifying application code.

    3. Better Maintainability

    Business logic remains separate from object creation, resulting in cleaner and more organized code.

    4. Improved Reusability

    Services can be reused across multiple parts of an application without duplicating code.

    5. Easier Refactoring

    Changing an implementation often requires updating only the Dependency Injection configuration rather than every class that uses the service.

    Best Practices

    • Prefer constructor injection over property injection.
    • Program against interfaces, not concrete classes.
    • Keep services focused on a single responsibility.
    • Use Scoped for most business services.
    • Avoid injecting too many dependencies into one class.
    • Never manually instantiate services that are managed by the DI container.
    • Use Singleton only for thread-safe shared services.

    Key Takeaway

    Dependency Injection is not a framework feature exclusive to ASP.NET Core—it is a software design pattern that promotes loose coupling and maintainable code.

    ASP.NET Core simply provides a powerful built-in Dependency Injection container that makes implementing this pattern straightforward.

    Leave a Comment

    Your email address will not be published. Required fields are marked *

    Scroll to Top