Monday, October 27, 2025

Dotnet Core - Dependency Injection

Dependency Injection (DI) is a design pattern that allows a class to receive its dependencies from external sources rather than creating them internally.

It’s part of the broader Inversion of Control (IoC) principle — meaning:
Instead of your class controlling its dependencies, something else (the DI container) provides them.



🔍 Simple Example (Without DI)

public class NotificationService { private readonly EmailService _emailService; public NotificationService() { _emailService = new EmailService(); // tightly coupled } public void SendNotification(string message) { _emailService.SendEmail(message); } }

Problem:

  • NotificationService is tightly coupled to EmailService.

  • If you want to switch to SmsService, you must modify and recompile this class.


✅ With Dependency Injection

public interface IMessageService { void SendMessage(string message); } public class EmailService : IMessageService { public void SendMessage(string message) { Console.WriteLine($"Email sent: {message}"); } } public class NotificationService { private readonly IMessageService _messageService; // Dependency injected via constructor public NotificationService(IMessageService messageService) { _messageService = messageService; } public void Notify(string message) { _messageService.SendMessage(message); } }

Now, the class depends on an abstraction (interface) — not a concrete implementation.


⚙️ How .NET Core Implements DI

.NET Core has a built-in dependency injection container — you don’t need external libraries like Autofac or Ninject (though you can still use them if needed).

You register your services in the Startup.cs (for .NET Core 3.1) or Program.cs (for .NET 6/7/8).


🧩 Example: Registering Services in .NET 6+

var builder = WebApplication.CreateBuilder(args); // Register services with DI container builder.Services.AddTransient<IMessageService, EmailService>(); builder.Services.AddScoped<NotificationService>(); var app = builder.Build(); app.MapGet("/notify", (NotificationService notification) => { notification.Notify("Hello from DI!"); return "Notification sent!"; }); app.Run();

🧱 Service Lifetime Scopes in .NET Core

When registering dependencies, you choose their lifetime — how long they live in memory.

LifetimeDescriptionExample Use Case
TransientCreated each time they are requested.Lightweight, stateless services.
ScopedCreated once per request (HTTP request).Web API services, database context.
SingletonCreated only once for the entire app lifetime.Configuration, caching, logging.

Example:

builder.Services.AddTransient<IEmailService, EmailService>(); // new every time builder.Services.AddScoped<IUserRepository, UserRepository>(); // per HTTP request builder.Services.AddSingleton<ILogger, ConsoleLogger>(); // one shared instance

🧩 How Dependencies Are Injected

.NET Core supports 3 injection methods:

TypeDescriptionExample
Constructor InjectionMost common — dependencies passed via constructor.public MyClass(IMyService service)
Method InjectionDependencies passed directly into method.void MyMethod(IMyService service)
Property InjectionDependencies assigned to public property (less common).public IMyService MyService { get; set; }

💡 Example in ASP.NET Core Controller

[ApiController] [Route("api/[controller]")] public class UserController : ControllerBase { private readonly IUserService _userService; // Constructor Injection public UserController(IUserService userService) { _userService = userService; } [HttpGet("{id}")] public IActionResult GetUser(int id) { var user = _userService.GetUserById(id); return Ok(user); } }

And in Program.cs:

builder.Services.AddScoped<IUserService, UserService>();

🧩 Real-Life Analogy

Think of DI like ordering coffee ☕:

  • You (the controller) need coffee (a dependency).

  • Instead of making it yourself (creating new CoffeeMaker()), you ask the barista (DI container) to provide one.

  • The barista knows which coffee maker to use (EmailService, SmsService, etc.).

  • You just consume it — no need to know how it’s made.


🧰 Benefits of Dependency Injection

Loose coupling – Easier to swap implementations.
Better testability – Mock dependencies in unit tests easily.
Simpler maintenance – Fewer code changes when dependencies evolve.
Centralized configuration – All service wiring is in one place.
Improved scalability – Encourages modular architecture.


🧪 Example: Unit Testing with DI

public class NotificationTests { [Fact] public void ShouldSendNotificationUsingEmail() { var mockMessageService = new Mock<IMessageService>(); var notificationService = new NotificationService(mockMessageService.Object); notificationService.Notify("Test"); mockMessageService.Verify(m => m.SendMessage("Test"), Times.Once); } }

No comments:

Post a Comment

CI/CD - Safe DB Changes/Migrations

Safe DB Migrations means updating your database schema without breaking the running application and without downtime . In real systems (A...