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.
Constructor injection
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
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
| Lifetime | One instance per… | Typical use |
|---|---|---|
AddSingleton | HttpClient factories, clocks | |
AddScoped | DbContext, unit of work, the current user | |
AddTransient |
Using the generic host
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();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
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
| Do | Avoid |
|---|---|
provider.GetService inside classes (the “service locator” anti-pattern hides dependencies) | |
new-ing services deep inside business code | |
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
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());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.