DEV Community

Saeid Ghaderi
Saeid Ghaderi

Posted on

πŸš€ Dependency Injection in ASP.NET Core β€” Beyond the Basics

Developers are familiar with the basics (like registering services in Startup.cs), many struggle to use it effectively in real-world applications.

In this post, we’ll go beyond the basics and look at advanced techniques, common mistakes, and how to design better, testable, and maintainable .NET applications using DI.

🧱 Quick Recap: What Is Dependency Injection?
Dependency Injection is a design pattern that allows objects to receive their dependencies from an external source rather than creating them internally. In ASP.NET Core, the built-in IoC (Inversion of Control) container manages this for you.

public interface IEmailService
{
    void Send(string to, string subject, string body);
}

public class SmtpEmailService : IEmailService
{
    public void Send(string to, string subject, string body)
    {
        // SMTP logic here
    }
}
Enter fullscreen mode Exit fullscreen mode

// Registering the service
services.AddScoped();
βœ… Common Service Lifetimes
ASP.NET Core provides three primary lifetimes:

Singleton: Created once for the application's lifetime

Scoped: Created once per request

Transient: Created every time it's requested

πŸ’‘ Tip: Avoid using Singleton for services that depend on Scoped dependencies like DbContext. This can lead to runtime exceptions.

⚠️ Anti-Patterns to Avoid

  1. Service Locator Pattern Using IServiceProvider manually to resolve dependencies is a code smell:

// DON'T DO THIS

public class SomeService
{
    private readonly IServiceProvider _provider;

    public SomeService(IServiceProvider provider)
    {
        _provider = provider;
    }

    public void DoSomething()
    {
        var db = _provider.GetService<MyDbContext>();
    }
}
Enter fullscreen mode Exit fullscreen mode

This hides dependencies, making your code harder to test and maintain.

βœ… Better: Use constructor injection instead.

πŸ§ͺ DI and Unit Testing
One of the biggest benefits of DI is testability.

Let’s say you have a controller that depends on IUserService:

public class UsersController : ControllerBase
{
    private readonly IUserService _userService;

    public UsersController(IUserService userService)
    {
        _userService = userService;
    }

    [HttpGet("{id}")]
    public IActionResult Get(int id)
    {
        var user = _userService.GetUserById(id);
        return Ok(user);
    }
}
Enter fullscreen mode Exit fullscreen mode

You can now easily mock IUserService in a unit test:

var mock = new Mock<IUserService>();
mock.Setup(u => u.GetUserById(1)).Returns(new User { Id = 1, Name = "John" });
Enter fullscreen mode Exit fullscreen mode
var controller = new UsersController(mock.Object);
var result = controller.Get(1);
Enter fullscreen mode Exit fullscreen mode

🧠 Tip: Use Interface Segregation for Better DI
Instead of large interfaces with many methods, split them into smaller, focused interfaces. This makes your services easier to mock and reduces coupling.

🧰 Useful Libraries for DI Enhancements
Scrutor β€” Adds assembly scanning for service registration
πŸ‘‰ services.Scan(...)

Autofac β€” More powerful container if you outgrow the built-in one

MediatR β€” Combine DI with CQRS pattern for better decoupling

βœ… Final Thoughts
Dependency Injection is more than just registering services. It’s a mindset that promotes loose coupling, better design, and easier testing.

By avoiding common anti-patterns and leveraging DI features smartly, you can build applications that are easier to maintain and extend over time.

πŸ’¬ What’s Next?
Want me to dive into Scoped pitfalls in async methods?

Curious about integrating Autofac or Scrutor in a clean architecture project?

Let me know in the comments β€” and if you found this helpful, consider giving it a ❀️!

πŸ“Œ Tags:
#dotnet #aspnetcore #dependency-injection #csharp #architecture

Top comments (0)