Aspnet Di
title: ASP.NET Core Dependency Injection — Complete Guide to DI Container description: 'Learn ASP.NET Core dependency injection: service lifetimes (Singleton, Scoped, Transient), registration methods, constructor injection, built-in container, and best practices.' date: 2026-06-28 lastmod: 2026-06-28 weight: 20 tags: [backend, aspnet]
ASP.NET Core has a built-in dependency injection container that manages object creation and lifetimes, enabling loose coupling through constructor injection and service registration.
## What You'll Learn
By the end of this tutorial, you'll register services with appropriate lifetimes, inject dependencies via constructors, understand the built-in container, implement the Options pattern, and design for testability.
## Real-World Use
A UserController injects IUserService and ILogger. The service injects IUserRepository and IMapper. DI wires everything together. Unit tests inject mock services easily.
## DI Learning Path
```mermaid
flowchart LR
A[Middleware] --> B[DI]
B --> C[Config]
C --> D[EF Core]
D --> E[Migrations]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Service Lifetimes
var builder = WebApplication.CreateBuilder(args);
// Transient: created every time injected (lightweight, stateless)
builder.Services.AddTransient<ITransientService, TransientService>();
// Scoped: created once per HTTP request
builder.Services.AddScoped<IScopedService, ScopedService>();
// Singleton: created once, shared across all requests
builder.Services.AddSingleton<ISingletonService, SingletonService>();
Constructor Injection
public interface IUserRepository
{
Task<User?> GetByIdAsync(int id);
}
public class UserRepository : IUserRepository
{
private readonly AppDbContext _db;
public UserRepository(AppDbContext db) => _db = db;
public async Task<User?> GetByIdAsync(int id) => await _db.Users.FindAsync(id);
}
public class UserService : IUserService
{
private readonly IUserRepository _repo;
private readonly ILogger<UserService> _logger;
public UserService(IUserRepository repo, ILogger<UserService> logger)
{
_repo = repo;
_logger = logger;
}
public async Task<User?> GetUserAsync(int id)
{
_logger.LogInformation("Fetching user {Id}", id);
return await _repo.GetByIdAsync(id);
}
}
// Registration
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IUserService, UserService>();
Registration Methods
// Register by interface
builder.Services.AddScoped<IInterface, Implementation>();
// Register concrete type
builder.Services.AddScoped<MyService>();
// Register with factory
builder.Services.AddScoped<IService>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
return new MyService(config["ApiKey"] ?? "");
});
// Register instance (singleton)
builder.Services.AddSingleton<IWeatherService>(new WeatherService("api-key"));
// Register multiple implementations
builder.Services.AddScoped<INotificationService, EmailService>();
builder.Services.AddScoped<INotificationService, SmsService>();
Options Pattern
public class EmailOptions
{
public const string SectionName = "Email";
public string SmtpServer { get; set; } = "";
public int Port { get; set; } = 587;
public string Username { get; set; } = "";
public string Password { get; set; } = "";
}
// Program.cs
builder.Services.Configure<EmailOptions>(builder.Configuration.GetSection("Email"));
// Usage
public class EmailService
{
private readonly EmailOptions _options;
public EmailService(IOptions<EmailOptions> options)
{
_options = options.Value;
}
}
Common Mistakes
1. Captive Dependencies
A Singleton consuming a Scoped service captures it for the app's lifetime. Scoped services must not be injected into Singletons.
2. Over-Registering
Registering every class as a service clutters the container. Only register services that need DI (external dependencies).
3. Service Locator Pattern
Injecting IServiceProvider (service locator) instead of using constructor injection. This hides dependencies and complicates testing.
4. Not Disposing Resources
The DI container disposes IDisposable services when their lifetime ends. Don't manually dispose injected services.
5. Ignoring Lifetime Choices
Using Singleton for stateful services that aren't thread-safe causes race conditions. Use Scoped for per-request services.
Practice Questions
1. What are the three service lifetimes?
Singleton (one instance), Scoped (one per request), Transient (new every injection).
2. When should you use Scoped lifetime?
For services tied to a request (DbContext, request-specific state). Created once per request.
3. How does the DI container resolve dependencies?
It examines constructor parameters and recursively resolves each dependency from registered services.
4. What is the Options pattern?
A way to use strongly-typed configuration classes injected via IOptions
5. Challenge: Register services properly for a controller that depends on a repository, which depends on DbContext.
builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(connStr));
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<ProductsController>();
FAQ
Mini Project: Service Registration Example
Create and register a complete service layer with proper lifetimes.
builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(connStr), ServiceLifetime.Scoped);
builder.Services.AddScoped<IProductRepo, ProductRepo>();
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddSingleton<ICacheService, MemoryCacheService>();
builder.Services.AddTransient<IEmailService, EmailService>();
What's Next
ASP.NET Core Configuration ASP.NET Core Entity Framework
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro