Skip to content

Aspnet Di

DodaTech 3 min read

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, with automatic reload support.

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

Is the DI container replaceable?

Yes. Replace IServiceProviderFactory with Autofac, Unity, or another container if you need advanced features.

Can I inject multiple implementations of the same interface?

Yes. Register multiple services and inject IEnumerable to get all implementations.

What happens if a dependency isn't registered?

The container throws an InvalidOperationException at runtime when trying to resolve the unregistered type.

How do I use DI in minimal APIs?

Inject services directly in the lambda parameters. The DI container resolves them automatically.

What is the difference between AddTransient and AddScoped?

Transient creates a new instance every injection (even within the same request). Scoped creates one per request.

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