Dependency Injection
50 minDependency Injection (DI) is a design pattern that implements Inversion of Control (IoC), where dependencies are provided to classes rather than created by them. Instead of classes creating their dependencies directly, dependencies are injected through constructors, properties, or methods. This inversion enables loose coupling, making code more testable and maintainable. Understanding DI enables professional application architecture. DI is fundamental to ASP.NET Core.
ASP.NET Core has a built-in DI container that manages the creation and lifetime of objects, eliminating the need for external DI containers in most cases. The container is configured in Program.cs (or Startup.cs) using `builder.Services`. Services are registered with lifetime options: Singleton (one instance for app lifetime), Scoped (one per request), or Transient (new instance each time). Understanding service lifetimes enables proper dependency management. The built-in container is sufficient for most applications.
DI promotes loose coupling, testability, and maintainability by enabling classes to depend on abstractions (interfaces) rather than concrete implementations. Loose coupling means classes aren't tightly bound to specific implementations. Testability is improved because dependencies can be mocked. Maintainability is improved because changes to implementations don't require changes to dependent classes. Understanding DI benefits enables better architecture.
Service registration uses `AddSingleton`, `AddScoped`, or `AddTransient` to specify lifetimes. Singleton services are created once and shared. Scoped services are created once per HTTP request. Transient services are created each time they're requested. Understanding lifetimes enables appropriate service registration. Choosing wrong lifetimes can cause bugs or performance issues.
Constructor injection is the preferred method in ASP.NET Core—dependencies are provided through constructor parameters. The DI container automatically resolves and injects dependencies when creating instances. Constructor injection makes dependencies explicit and required. Understanding constructor injection enables effective DI usage. Constructor injection is the standard approach.
Best practices include using interfaces for dependencies, registering services in Program.cs, using appropriate lifetimes, avoiding service locator pattern, and understanding that DI is built into ASP.NET Core. Understanding DI enables maintainable, testable applications. DI is essential for professional ASP.NET Core development.
Key Concepts
- Dependency Injection implements Inversion of Control.
- ASP.NET Core has a built-in DI container.
- DI promotes loose coupling, testability, and maintainability.
- Service lifetimes: Singleton, Scoped, Transient.
- Constructor injection is the preferred method.
Learning Objectives
Master
- Understanding Dependency Injection principles
- Registering services with the DI container
- Using constructor injection
- Choosing appropriate service lifetimes
Develop
- Understanding IoC and DI patterns
- Designing loosely coupled architectures
- Appreciating DI's role in maintainability
Tips
- Use interfaces for dependencies to enable loose coupling.
- Use Scoped lifetime for DbContext and request-scoped services.
- Use Singleton for stateless services that are expensive to create.
- Use Transient for lightweight, stateless services.
Common Pitfalls
- Using wrong service lifetimes, causing bugs or performance issues.
- Not using interfaces, creating tight coupling.
- Using service locator pattern instead of constructor injection.
- Not understanding lifetimes, causing memory leaks or bugs.
Summary
- Dependency Injection implements Inversion of Control.
- ASP.NET Core has a built-in DI container.
- DI promotes loose coupling, testability, and maintainability.
- Understanding DI enables professional application architecture.
- DI is fundamental to ASP.NET Core.
Exercise
Create a service and inject it into a controller.
// IProductService.cs
public interface IProductService
{
Task<IEnumerable<Product>> GetAllProductsAsync();
}
// ProductService.cs
public class ProductService : IProductService
{
private readonly ApplicationDbContext _context;
public ProductService(ApplicationDbContext context)
{
_context = context;
}
public async Task<IEnumerable<Product>> GetAllProductsAsync()
{
return await _context.Products.ToListAsync();
}
}
// Program.cs
builder.Services.AddScoped<IProductService, ProductService>();
// Controller
public class ProductsController : Controller
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
public async Task<IActionResult> Index()
{
var products = await _productService.GetAllProductsAsync();
return View(products);
}
}