Home Understanding Dependency Injection in .NET
Post
Cancel

Understanding Dependency Injection in .NET

Dependency Injection (DI) is a fundamental design pattern in modern .NET development. It allows you to write loosely coupled, maintainable, and testable code by providing dependencies to classes from an external source rather than having them create or manage those dependencies themselves.

In this article, we’ll explore what DI is, why it’s important, how it works in .NET, and walk through hands-on examples and best practices.


What Is Dependency Injection?

Dependency Injection is a technique where a class receives the services or objects it depends on (its dependencies) from an external system, instead of instantiating them directly.

For example, rather than doing this:

1
2
3
4
public class OrderService
{
    private readonly PaymentService _paymentService = new PaymentService();
}

You do this:

1
2
3
4
5
6
7
8
9
public class OrderService
{
    private readonly PaymentService _paymentService;

    public OrderService(PaymentService paymentService)
    {
        _paymentService = paymentService;
    }
}

Now OrderService depends on an injected PaymentService.


Why Use DI?

Loose coupling — classes are not tightly bound to specific implementations.
Easier testing — inject mock or stub services in unit tests.
Better maintainability — replace implementations without changing dependent code.
Centralized configuration — control lifetimes and setups in one place.


DI in .NET

ASP.NET Core has built-in dependency injection support via the built-in IoC (Inversion of Control) container.

You register services in Program.cs:

1
builder.Services.AddTransient<IMyService, MyService>();

And inject them where needed:

1
2
3
4
5
6
7
8
9
public class MyController : ControllerBase
{
    private readonly IMyService _service;

    public MyController(IMyService service)
    {
        _service = service;
    }
}

Service Lifetimes

LifetimeDescription
TransientNew instance every time requested
ScopedOne instance per request (for web apps)
SingletonSingle instance shared for the lifetime of the app

Example:

1
2
3
builder.Services.AddSingleton<ISingletonService, SingletonService>();
builder.Services.AddScoped<IScopedService, ScopedService>();
builder.Services.AddTransient<ITransientService, TransientService>();

Hands-On Example

Step 1: Define an Interface

1
2
3
4
public interface IMessageService
{
    string GetMessage();
}

Step 2: Implement the Service

1
2
3
4
public class HelloWorldMessageService : IMessageService
{
    public string GetMessage() => "Hello, Dependency Injection!";
}

Step 3: Register and Inject

1
builder.Services.AddScoped<IMessageService, HelloWorldMessageService>();

In a controller or Razor Page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HomeController : Controller
{
    private readonly IMessageService _messageService;

    public HomeController(IMessageService messageService)
    {
        _messageService = messageService;
    }

    public IActionResult Index()
    {
        ViewBag.Message = _messageService.GetMessage();
        return View();
    }
}

Constructor Injection vs. Alternatives

Constructor injection — most common and recommended.
✅ Property injection — for optional dependencies.
✅ Method injection — pass dependencies via method parameters.

Example of property injection:

1
public IOptionalService OptionalService { get; set; }

Testing with Dependency Injection

DI makes testing easier by allowing you to inject mocks.

Example with Moq:

1
2
3
4
var mock = new Mock<IMessageService>();
mock.Setup(s => s.GetMessage()).Returns("Test Message");

var controller = new HomeController(mock.Object);

Now you can test without touching the real implementation.


Best Practices

✅ Always program against interfaces, not concrete classes.
✅ Keep services stateless unless they are singletons.
✅ Register services at the correct lifetime.
✅ Avoid service locator anti-pattern (i.e., avoid pulling services manually from IServiceProvider).
✅ Use DI even in non-ASP.NET Core apps by wiring up IServiceCollection manually.


Summary

Dependency Injection is a cornerstone of modern .NET application design, promoting loose coupling, testability, and maintainability. By mastering DI, you set the foundation for building scalable and robust applications that are easy to evolve and maintain over time.

This post is licensed under CC BY 4.0 by the author.

What is SQL Window Function? How to Use It in Analysis

What are High-Value Customers? How to Find Them Using SQL