CSC367 NET Centric Computing

NET Centric ComputingUnit 88 min read

Dependency Injection (DI) & IoC Containers: Core Concepts, Lifecycle, and Real-World Use

Unit 8 of NET Centric Computing covers Dependency Injection (DI) principles, Inversion of Control (IoC) containers, service lifetimes (transient, scoped, singleton), registration patterns, and how DI solves tight coupling in ASP.NET Core. Includes real-world examples from eSewa, Ncell, and WhatsApp, plus a worked examp

What is Dependency Injection (DI)?

Dependency Injection is a design pattern that removes hard-coded dependencies and makes an application more modular, testable, and maintainable. Instead of a class creating its own dependencies, an external entity (the IoC container) provides them. This follows the Dependency Inversion Principle (DIP) from SOLID principles.

1980sEarly OOP: Manualdependency management 1990sFrameworksintroduce inversion of2000sDI containersemerge (Spring, .NET C2010s–PresentModern DI/IoC inASP.NET Core, Angular,
Evolution of Dependency Injection in software development

Why Use DI?

  • Decouples components: Classes don’t need to know how to create their dependencies.
  • Easier testing: Dependencies can be mocked or replaced.
  • Reusability: Components can be reused in different contexts.
  • Maintainability: Changes in one part don’t break others.
classDiagram
    class OrderProcessor {
        +ProcessOrder()
    }
    class PaymentService {
        +Charge()
    }
    class EmailService {
        +SendConfirmation()
    }
    OrderProcessor --> PaymentService : "uses"
    OrderProcessor --> EmailService : "uses"
    note for OrderProcessor "Dependencies are injected, not created internally"

Inversion of Control (IoC) Containers

An IoC container is the engine that manages object creation, lifetime, and dependency resolution. ASP.NET Core’s built-in DI container is an IoC container. It:

  1. Registers services (e.g., services.AddTransient<IPaymentService, PaymentService>()).
  2. Resolves dependencies when requested (e.g., via constructor injection).
  3. Manages lifetimes (transient, scoped, singleton).

How IoC Works in ASP.NET Core

  1. Registration: Services are registered in Program.cs or Startup.cs.
  2. Resolution: The container creates and injects dependencies when a class is instantiated.
  3. Disposal: The container handles cleanup (e.g., IDisposable objects).
// Example: Registering a service in Program.cs
builder.Services.AddTransient<IPaymentService, PaymentService>();
builder.Services.AddScoped<IEmailService, EmailService>();

Service Lifetimes in DI

The IoC container manages three types of service lifetimes:

0255075100Transient1Scoped10Singleton100
Relative instance counts per request (hypothetical example: 100 requests)
Lifetime Type Scope Example Use Case When to Use
Transient New instance per request IEmailService (stateless operations) Stateless services (e.g., logging)
Scoped One instance per HTTP request IOrderRepository (shared per request) Database contexts, request-specific data
Singleton One instance for the app IConfiguration (global settings) Caching, configuration, app-wide services

Real-World Applications of DI

1. eSewa (Nepal)

  • Use Case: Payment processing and user authentication.
  • DI in Action:
    • IPaymentGateway is injected into OrderService (transient lifetime).
    • IUserRepository is scoped to ensure data consistency per transaction.
    • Why? eSewa can swap payment providers (e.g., from Khalti to IME Pay) without changing OrderService.

2. WhatsApp (Global)

  • Use Case: Message routing and encryption.
  • DI in Action:
    • IMessageEncryptor is injected into MessageHandler (singleton for performance).
    • IUserSession is scoped per chat session.
    • Why? WhatsApp can update encryption libraries without breaking message handling.

3. Ncell (Nepal)

  • Use Case: Billing and customer service.
  • DI in Action:
    • IBillingService is registered as scoped to avoid duplicate charges.
    • ILogger is transient for thread safety.
    • Why? Ncell can scale billing services independently.

Worked Example: Loan Approval System

Scenario: A bank’s loan approval system uses DI to separate validation, credit scoring, and notification logic.

Step 1: Define Interfaces and Classes

public interface ICreditScorer { bool IsEligible(Customer customer); }
public interface ILoanValidator { bool Validate(Customer customer); }
public interface INotificationService { void SendApprovalEmail(Customer customer); }

public class LoanService {
    private readonly ICreditScorer _scorer;
    private readonly ILoanValidator _validator;
    private readonly INotificationService _notifier;

    // Dependencies are injected via constructor
    public LoanService(ICreditScorer scorer, ILoanValidator validator, INotificationService notifier) {
        _scorer = scorer;
        _validator = validator;
        _notifier = notifier;
    }

    public void ApproveLoan(Customer customer) {
        if (_validator.Validate(customer) && _scorer.IsEligible(customer)) {
            _notifier.SendApprovalEmail(customer);
        }
    }
}

Step 2: Register Services in Program.cs

builder.Services.AddTransient<ICreditScorer, CreditScorer>();
builder.Services.AddScoped<ILoanValidator, LoanValidator>();
builder.Services.AddSingleton<INotificationService, EmailNotificationService>();

Step 3: Resolve Dependencies

The IoC container automatically injects the correct implementations when LoanService is created:

var loanService = app.Services.GetRequiredService<LoanService>();
loanService.ApproveLoan(new Customer { Name = "Ramesh", CreditScore = 750 });

DI vs. Traditional Coupling

Traditional Approach (Tight Coupling) Dependency Injection (Loose Coupling)
Class creates its own dependencies. Dependencies are injected externally.
Hard to test (mocking is difficult). Easy to mock dependencies for unit tests.
Changes in one class break others. Changes are isolated.
Example: PaymentService creates EmailService. OrderProcessor receives IPaymentService and IEmailService.

Common Pitfalls and Best Practices

❌ Anti-Patterns

  1. Overusing Singleton: Can lead to shared state issues (e.g., caching stale data).
  2. Circular Dependencies: Class A depends on B, which depends on A.
  3. Ignoring Disposal: Forgetting to implement IDisposable for scoped services.

✅ Best Practices

  1. Prefer Interfaces: Always inject interfaces, not concrete classes.
  2. Use Constructor Injection: More explicit than property injection.
  3. Avoid Service Locator: Directly resolving dependencies in classes violates DI.
  4. Name Services Clearly: Use suffixes like Service, Repository, or Manager.

The DI Container Lifecycle


Exam Tip

What Examiners Look For:

  1. Definitions:
    • Clearly distinguish between DI (pattern) and IoC (container).
    • Explain transient, scoped, and singleton lifetimes with examples.
  2. Code Examples:
    • Show registration (AddTransient, AddScoped, AddSingleton).
    • Demonstrate constructor injection in a class.
  3. Real-World Scenarios:
    • Relate DI to eSewa’s payment system, WhatsApp’s message routing, or Ncell’s billing.
    • Explain how DI enables mocking in unit tests (e.g., replacing IEmailService with a fake).
  4. Common Mistakes:
    • Avoid vague answers like “DI improves maintainability.” Instead, say: “DI decouples OrderService from PaymentGateway, allowing Ncell to switch providers without modifying OrderService.”
  5. Diagrams:
    • Draw a class diagram showing interfaces and implementations.
    • Sketch the IoC container lifecycle (register → resolve → dispose).

Past Exam Questions Answered:

  1. Life Cycle of DI Container:
    • Registration → Resolution → Disposal (see lifecycle diagram above).
  2. SQL Injection Prevention:
    • Use parameterized queries (not string concatenation) and Entity Framework Core (which escapes inputs by default).
    • Example:
      // Vulnerable (SQL Injection risk)
      var query = $"SELECT * FROM Users WHERE Username = '{username}'";
      
      // Safe (Parameterized Query)
      var user = await _context.Users.FirstOrDefaultAsync(u => u.Username == username);
      
  3. Types of Services Managed by IoC:
    • Transient: New instance every time (e.g., ILogger).
    • Scoped: One instance per request (e.g., DbContext).
    • Singleton: One instance for the app (e.g., IConfiguration).

Based on the TU BSc CSIT syllabus for NET Centric Computing (CSC367), unit 8.

Discussion

Loading…