C# Academy · Lesson

DI Container Fundamentals

Understand inversion of control, service registration, and how the .NET DI container resolves dependencies.

Lesson 1 of 413 steps

DI Container Fundamentals is a free C# Academy lesson on CoddyKit. This is lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, and your progress syncs across the web and the CoddyKit app. The C# Academy course includes 4 lessons in total.

What Is Dependency Injection?

Dependency Injection (DI) is a design pattern where a class receives its dependencies from an external source rather than creating them itself.

Instead of writing var repo = new UserRepository(); inside a class, the class declares what it needs and the DI container provides it.

Inversion of Control

DI is a concrete implementation of the Inversion of Control (IoC) principle: the control of creating dependencies is inverted from the dependent class to an external container.

This makes classes easier to test and replace.

Setting Up the .NET DI Container

In a .NET app, the DI container is configured via IServiceCollection in Program.cs. Services are registered and then resolved by the runtime.

var builder = WebApplication.CreateBuilder(args);

// Register services
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<UserService>();

var app = builder.Build();
app.Run();

Defining an Interface & Implementation

DI works best when you program to interfaces. Define an interface for the service contract and a concrete class that implements it.

public interface IUserRepository
{
    User? GetById(int id);
}

public class UserRepository : IUserRepository
{
    public User? GetById(int id)
    {
        // fetch from database
        return new User { Id = id, Name = "Alice" };
    }
}

Resolving Services via Constructor

The container automatically injects registered services into constructors. The class only needs to declare the interface it needs — no new keyword required.

public class UserService
{
    private readonly IUserRepository _repo;

    public UserService(IUserRepository repo)
    {
        _repo = repo;
    }

    public User? FindUser(int id) => _repo.GetById(id);
}

Registering Multiple Services

You can register as many services as needed. Common extension methods are AddTransient, AddScoped, and AddSingleton — each defining a different lifetime.

builder.Services.AddSingleton<IConfig, AppConfig>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddTransient<IEmailService, SmtpEmailService>();
builder.Services.AddScoped<UserService>();

Using IServiceProvider Directly

In rare cases you need to resolve services imperatively. IServiceProvider lets you call GetRequiredService<T>() to resolve a service, but prefer constructor injection in most scenarios.

// Anti-pattern (service locator) — use sparingly
public class ManualResolver
{
    private readonly IServiceProvider _sp;

    public ManualResolver(IServiceProvider sp) => _sp = sp;

    public void DoWork()
    {
        var svc = _sp.GetRequiredService<UserService>();
        svc.FindUser(1);
    }
}

Registering Open Generics

.NET DI supports open generic registrations. You can register IRepository<> once and it resolves to Repository<T> for any entity type.

builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));

// Resolves as:
// IRepository<User> -> Repository<User>
// IRepository<Order> -> Repository<Order>

Validation at Startup

With ValidateOnStart() or ValidateScopes = true, the container can detect misconfigured registrations (like capturing a Scoped service inside a Singleton) at application start instead of at runtime.

builder.Services.AddScoped<IMyService, MyService>();

// Detect scope violations at startup
builder.Host.UseDefaultServiceProvider(options =>
{
    options.ValidateScopes = true;
    options.ValidateOnBuild = true;
});

Decorating Services

You can wrap a service with a decorator without modifying the original class. Register the inner service first, then register the decorator that wraps it.

builder.Services.AddScoped<IUserRepository, UserRepository>();

// Decorator pattern
builder.Services.Decorate<IUserRepository, CachedUserRepository>();
// Requires Scrutor NuGet package

Real-World: Minimal API with DI

In a Minimal API, services are injected directly into route handler parameters. The container resolves them automatically.

app.MapGet("/users/{id}", (int id, UserService svc) =>
{
    var user = svc.FindUser(id);
    return user is null ? Results.NotFound() : Results.Ok(user);
});

Quick Check

Which method should you prefer for obtaining dependencies in most classes?

Recap: DI Container Fundamentals

Key takeaways:

  • DI inverts dependency creation: the container builds objects, not the class itself
  • Register services with AddTransient / AddScoped / AddSingleton
  • Constructor injection is the standard and preferred approach
  • Open generics, decorators, and ValidateOnBuild are powerful advanced features
Free to start

Learn C# with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
93
Lessons
346

Frequently Asked Questions

Is the “DI Container Fundamentals” lesson free?

Yes — the full text of “DI Container Fundamentals” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO. The C# Academy course includes 4 lessons in total.

What will I learn in “DI Container Fundamentals”?

Understand inversion of control, service registration, and how the .NET DI container resolves dependencies. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 1 of 4.

How long does the “DI Container Fundamentals” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. DI Container Fundamentals
  2. Service Lifetimes: Transient, Scoped, Singleton
  3. Constructor Injection & Interfaces
  4. Factory & Options Patterns
← Back to C# Academy