<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. C#
  3. Master level
All lessons in this course

Lesson 55 of 60 17 minMaster

Dependency injection

Let a container create your objects: constructor injection, IServiceCollection, Singleton/Scoped/Transient lifetimes, captive dependencies and using DI in console and WPF apps.

In the SOLID lesson, classes received their dependencies through the constructor. Someone still has to create all those objects in the right order. In a real app that is dozens of classes. A DI container does it for you: you register which class implements which interface, and the container builds whole object graphs on request.

Constructor injection

Services.cs
public interface IClock { DateTime Now { get; } }public class SystemClock : IClock { public DateTime Now => DateTime.Now; } public class ReportBuilder(IUnitOfWork uow, IClock clock) : IReportBuilder{    public Report Build() => new(uow.Beams.ToList(), clock.Now);}

ReportBuilder just asks for what it needs. It doesn’t know which clock or which database it gets, so tests can pass a fake clock showing any date.

Registering and resolving

Program.cs
using Microsoft.Extensions.DependencyInjection;   // dotnet add package Microsoft.Extensions.DependencyInjection var services = new ServiceCollection();services.AddSingleton<IClock, SystemClock>();services.AddScoped<IUnitOfWork, UnitOfWork>();services.AddTransient<IReportBuilder, ReportBuilder>(); using ServiceProvider provider = services.BuildServiceProvider(validateScopes: true); using (IServiceScope scope = provider.CreateScope()){    var builder = scope.ServiceProvider.GetRequiredService<IReportBuilder>();    Report r = builder.Build();     // the container created UnitOfWork and SystemClock for us}

Lifetimes

Interactive

The container and lifetimes

Each request creates a scope and resolves ReportController(IReportBuilder, IUnitOfWork). ReportBuilder itself needs IUnitOfWork and IClock.

Request 1 (scope 1)
ReportController
IReportBuilder → ReportBuilder #1 (Transient)
IUnitOfWork → UnitOfWork #1 (Scoped)
IClock → SystemClock #1 (Singleton)
IUnitOfWork → UnitOfWork #1 (reused) (Scoped)
Request 2 (scope 2)
ReportController
IReportBuilder → ReportBuilder #2 (Transient)
IUnitOfWork → UnitOfWork #2 (Scoped)
IClock → SystemClock #1 (reused) (Singleton)
IUnitOfWork → UnitOfWork #2 (reused) (Scoped)
Singleton: one for the app. Scoped: one per request, shared inside it. Transient: a new one every time it is asked for.
LifetimeOne instance per…Typical use
AddSingletonapplicationsettings, caches, HttpClient factories, clocks
AddScopedscope (one web request, one window or one operation)DbContext, unit of work, the current user
AddTransientevery time it is requestedlightweight, stateless services

Using the generic host

Most .NET apps set up DI, configuration and logging together with the host:

Program.cs
using Microsoft.Extensions.Hosting;   // dotnet add package Microsoft.Extensions.Hosting var builder = Host.CreateApplicationBuilder(args);builder.Services.AddSingleton<IClock, SystemClock>();builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();builder.Services.AddTransient<IReportBuilder, ReportBuilder>();builder.Services.AddTransient<ExportCommand>(); using IHost host = builder.Build(); using var scope = host.Services.CreateScope();await scope.ServiceProvider.GetRequiredService<ExportCommand>().RunAsync();

In ASP.NET Core this is WebApplication.CreateBuilder(args), and the framework creates one scope per HTTP request for you. In WPF you build the host in App.OnStartup and resolve the main window: host.Services.GetRequiredService<MainWindow>().Show();.

Registering several implementations

Many.cs
services.AddTransient<IReportWriter, PdfReportWriter>();services.AddTransient<IReportWriter, ExcelReportWriter>(); public class ExportService(IEnumerable<IReportWriter> writers)   // gets BOTH{    public IEnumerable<string> Formats => writers.Select(w => w.Extension);}

Good habits

DoAvoid
ask for interfaces in the constructorcalling provider.GetService inside classes (the “service locator” anti-pattern hides dependencies)
keep constructors cheap: just store the dependenciesdoing I/O or heavy work in constructors
register everything in one place at startupnew-ing services deep inside business code
treat many constructor parameters (6+) as a warningone class that needs everything: split it (SRP)
Quick check

Inside one web request, a controller and a repository both ask for AppDbContext, registered with AddScoped. What do they get?

Quick check

Why is a singleton that depends on a scoped service a problem?

Practice

Register IClock as a singleton (SystemClock) and Greeter as transient. Greeter takes IClock and has string Hello() returning "Good morning" before 12:00, otherwise "Good afternoon". Resolve and print it. Then write a FixedClock you could use in a test.

Show the solution
Solution.cs
public interface IClock { DateTime Now { get; } }public class SystemClock : IClock { public DateTime Now => DateTime.Now; }public class FixedClock(DateTime now) : IClock { public DateTime Now => now; } public class Greeter(IClock clock){    public string Hello() => clock.Now.Hour < 12 ? "Good morning" : "Good afternoon";} var services = new ServiceCollection()    .AddSingleton<IClock, SystemClock>()    .AddTransient<Greeter>();using var provider = services.BuildServiceProvider();Console.WriteLine(provider.GetRequiredService<Greeter>().Hello()); // in a test: no container neededConsole.WriteLine(new Greeter(new FixedClock(new DateTime(2025, 1, 1, 9, 0, 0))).Hello());
Output
Good afternoon   (depends on when you run it)
Good morning

Remember

  • Classes ask for interfaces in their constructor; the container creates and wires them.
  • Singleton: one per app. Scoped: one per scope/request. Transient: new each time.
  • Never let a longer-lived service capture a shorter-lived one; enable scope validation.
  • Register at startup with the host; avoid resolving services by hand inside business code.
0 of 60 lessons completed