Friday, November 7, 2025

Dotnet Core - Dependency Injection (DI)

 πŸ§© What is Dependency Injection (DI)?

Dependency Injection (DI) is a design pattern used to achieve loose coupling between classes and their dependencies.

In simple terms:

Instead of a class creating its own dependencies, they are provided (injected) from the outside — typically by a framework or container.

 




🧠 Example Without DI (Tight Coupling)

public class NotificationService { private readonly EmailService _emailService = new EmailService(); public void Notify(string message) { _emailService.Send(message); } }

Here:

  • NotificationService directly depends on EmailService.
  • If we want to replace EmailService with, say, SmsService, we must change the code inside NotificationService.


✅ Example With DI (Loose Coupling)

Step 1: Define Interface
public interface INotification
{
void Notify(string msg);
}


Step 2: Implement Email & SMS
public class EmailNotification : INotification
{
public void Notify(string msg)
{
Console.WriteLine("Email: " + msg);
}
}

public class SMSNotification : INotification
{
public void Notify(string msg)
{
Console.WriteLine("SMS: " + msg);
}
}


Step 3: Create NotificationService (Core Logic)

Instead of injecting just one service, inject both:

public class NotificationService
{
private readonly EmailNotification _email;
private readonly SMSNotification _sms;

public NotificationService(EmailNotification email, SMSNotification sms) //DI
{
_email = email;
_sms = sms;
}

public void SendEmail(string msg)
{
_email.Notify(msg);
}

public void SendSMS(string msg)
{
_sms.Notify(msg);
}
}

Step4: Register with DI

builder.Services.AddScoped<EmailNotification>();
builder.Services.AddScoped<SMSNotification>();
builder.Services.AddScoped<NotificationService>();


Step 5: Use in Controller / App
public class HomeController
{
private readonly NotificationService _notificationService;

public HomeController(NotificationService notificationService) //DI
{
_notificationService = notificationService;
}

public void Test()
{
_notificationService.SendEmail("Hello via Email");
_notificationService.SendSMS("Hello via SMS");
}
}

Now you can plug in any implementation (Email, SMS, Push) without changing NotificationService.


🧱 Dependency Injection in .NET Core

.NET Core has built-in support for Dependency Injection via the Microsoft.Extensions.DependencyInjection namespace.

You configure DI inside the Program.cs or Startup.cs (depending on .NET version).


🧰 Typical .NET Core DI Setup

Example — in Program.cs (.NET 6/7/8 minimal hosting model)

var builder = WebApplication.CreateBuilder(args);

// 1️⃣ Register services in DI container builder.Services.AddScoped<IMessageService, EmailService>(); builder.Services.AddScoped<NotificationService>(); var app = builder.Build(); // 2️⃣ Resolve and use service app.MapGet("/notify", (NotificationService service) => { service.Notify("Hello, .NET Core DI!"); return "Notification Sent!"; }); app.Run();

πŸ” How DI Works in .NET Core

When you run the app:

  1. The service container is created by WebApplication.CreateBuilder.
  2. Services are registered using builder.Services.Add...().
  3. When a class (e.g., controller, page, or middleware) requests a dependency, the DI container creates and injects it automatically.


πŸ”„ Service Lifetimes

LifetimeDescriptionUse Case
TransientNew instance created every time it’s requestedLightweight, stateless services
ScopedOne instance per request (HTTP scope)Web request-specific services
SingletonSingle instance for the entire app lifetimeConfiguration, caching, logging

Example:

builder.Services.AddTransient<IEmailService, EmailService>(); builder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddSingleton<ILoggingService, LoggingService>();

🧠 DI Example in ASP.NET Core Controller

public interface ICustomerService { IEnumerable<string> GetCustomers(); } public class CustomerService : ICustomerService { public IEnumerable<string> GetCustomers() => new[] { "John", "Jane" }; } [ApiController] [Route("[controller]")] public class CustomerController : ControllerBase { private readonly ICustomerService _service; // Dependency is injected automatically public CustomerController(ICustomerService service) { _service = service; } [HttpGet] public IActionResult Get() { return Ok(_service.GetCustomers()); } }

✅ ASP.NET Core automatically injects CustomerService because it was registered in the DI container.


⚙️ Advanced DI Topics

1️⃣ Constructor Injection (most common)

Dependencies passed via constructor.

2️⃣ Method Injection

Dependencies passed as method parameters.

public void SendNotification([FromServices] IMessageService service) { service.Send("Message"); }

3️⃣ Property Injection

Dependency assigned via property (not common in .NET Core DI by default).


πŸ”© Built-in DI Container Features

  • Open Generics Support
    builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));

    DI Container creates instance of entity (User in below example) and provide when referred 

             public class UserController
        {
            private readonly IRepository<User> _repo;

            public UserController(IRepository<User> repo)
            {
                _repo = repo;
            }
        }


  • Service Replacement:  Simply remove MyService registration with IMyService and add new registration MyMockService with IMyService.
    This is useful when you are using some library and that has registered IMyService with MyService, so now Replace statement will replace all previous registration with new one.

    builder.Services.AddSingleton<IMyService, MyService>();
    builder.Services.Replace(ServiceDescriptor.Singleton<IMyService, MyMockService>());

  • Configuration + Options Pattern
    builder.Services.Configure<MyConfig>(builder.Configuration.GetSection("MySection"));


🧰 DI in Non-Web .NET Apps

You can also use DI in:

  • Console apps
  • Worker services
  • Background jobs

Example:

var services = new ServiceCollection(); services.AddScoped<IMessageService, EmailService>(); var provider = services.BuildServiceProvider(); var messageService = provider.GetRequiredService<IMessageService>(); messageService.Send("Hello from Console App!");

Benefits of Using DI

BenefitDescription
Loose CouplingEasier to swap implementations
TestabilityUse mocks in unit tests easily
ReusabilityServices can be reused across components
MaintainabilityReduces hard-coded dependencies
ScalabilityCentralized lifecycle management

⚠️ Common Pitfalls

MistakeImpact
Registering service with wrong lifetimeMemory leaks or inconsistent behavior
Creating new instances manually inside serviceBreaks DI chain
Overusing SingletonCan cause thread-safety issues
Circular dependenciesTwo services depend on each other — runtime error

Summary

AspectDetails
PatternDependency Injection (DI)
Built-in ContainerMicrosoft.Extensions.DependencyInjection
Registration MethodsAddSingleton, AddScoped, AddTransient
GoalLoose coupling, better testability, and flexibility
Injection MethodsConstructor (primary), Method, Property
Used InASP.NET Core, Worker Services, Blazor, Console Apps

No comments:

Post a Comment

Node | Cluster Vs Worker Threads

Cluster: Multiple processes (scale app across CPU cores) Worker Threads: Multiple threads (handle CPU-heavy work inside one process) Cluster...