Episode ini mengajarkan dependency injection di .NET: container Microsoft.Extensions.DependencyInjection, lifetimes scoped, transient, dan singleton, pola registrasi layanan, serta cara mengonfigurasi layanan di dalam Generic Host. DI adalah tulang punggung aplikasi .NET modern.

Di episode 5 kalian belajar interface; episode 8 menunjukkan kenapa interface begitu penting di .NET: mereka adalah bahan utama dependency injection (DI). DI adalah pola di mana object menerima dependensinya dari luar, bukan menciptakannya sendiri — dan container DI .NET mengelola seluruh proses ini.
Dengan DI, kode menjadi mudah diuji, mudah diganti, dan struktur aplikasi menjadi jelas. Container Microsoft.Extensions.DependencyInjection sudah tertanam di Generic Host, sehingga setiap aplikasi .NET modern memakainya tanpa konfigurasi tambahan.
Container menyimpan registrasi berupa pasangan kontrak dan implementasi. Saat sebuah layanan diminta, container membuat implementasi yang sesuai dan menyuntikkan dependensinya. Contoh layanan dan kontraknya:
public interface IEmailSender
{
Task KirimAsync(string ke, string pesan);
}
public class SmtpEmailSender : IEmailSender
{
public Task KirimAsync(string ke, string pesan)
{
Console.WriteLine($"Email ke {ke}: {pesan}");
return Task.CompletedTask;
}
}SmtpEmailSender mengimplementasikan IEmailSender. Kode konsumen hanya bergantung pada interface, sehingga implementasi bisa diganti tanpa mengubah pemanggil — misalnya dari SMTP ke layanan cloud.
Registrasi dilakukan pada IServiceCollection di dalam Program.cs:
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IEmailSender, SmtpEmailSender>();
var host = builder.Build();builder.Services.AddSingleton<IEmailSender, SmtpEmailSender>() memberi tahu container: saat IEmailSender diminta, kembalikan SmtpEmailSender. builder.Build() mengonversi collection menjadi container yang siap digunakan.
Lifetime menentukan berapa lama sebuah instance hidup:
builder.Services.AddTransient<IPesanan, PesananService>();
builder.Services.AddScoped<IKontek, KontekService>();
builder.Services.AddSingleton<ILog, LogService>();AddTransient, AddScoped, dan AddSingleton adalah tiga method registrasi utama. Aturan emasnya: scoped tidak boleh disuntikkan ke singleton — karena singleton hidup lebih lama dari scope, kebocoran referensi akan merusak perilaku dan memori.
Panduan:
Konsumsi layanan dilakukan lewat konstruktor — container mengisinya otomatis:
public class OrderService
{
private readonly IEmailSender _sender;
public OrderService(IEmailSender sender)
{
_sender = sender;
}
public async Task PesanAsync(string email)
{
await _sender.KirimAsync(email, "Pesanan diterima");
}
}OrderService meminta IEmailSender lewat konstruktor. Container mendeteksi parameter ini dan menyuntikkan instance yang sesuai. Constructor injection adalah bentuk DI yang paling umum dan paling mudah diuji.
Untuk pilihan implementasi saat runtime, gunakan factory:
builder.Services.AddSingleton<IEmailSender>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
return config["Mail:Provider"] == "SendGrid"
? new SendGridEmailSender()
: new SmtpEmailSender();
});Factory menerima IServiceProvider (sp) sehingga bisa membaca layanan lain — di sini IConfiguration — sebelum memutuskan implementasi. Pattern ini berguna untuk provider yang berbeda antar environment.
Semua layanan host — logging, configuration, dan kustom — diregistrasi lewat builder.Services:
using var host = Host.CreateApplicationBuilder(args)
.Build();
var sender = host.Services.GetRequiredService<IEmailSender>();
await sender.KirimAsync("admin@example.com", "Test");host.Services.GetRequiredService<IEmailSender>() mengambil layanan langsung dari container — berguna di titik masuk aplikasi. Untuk kode biasa, andalkan constructor injection; mengakses container secara langsung sebaiknya dibatasi.
Aktifkan scope validation untuk mendeteksi registrasi yang salah lebih awal:
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddScoped<IData, DataService>();
builder.Services.AddSingleton<ICache, CacheService>();AddScoped pada layanan yang bergantung pada AddSingleton valid; sebaliknya singleton yang bergantung pada scoped akan memicu error saat scope validation aktif. Biasakan menjalankan aplikasi di development dengan validasi ini agar masalah lifetime ketahuan sebelum produksi.
Warning
Perhatikan lifetime saat menyuntikkan DbContext. Defaultnya scoped agar satu request memakai satu context — menyimpannya di singleton akan membuat connection dan state bocor antar request.
Inti yang harus dibawa pulang:
Di episode 9 selanjutnya kita akan membahas data access dan persistence — Entity Framework Core, DbContext, mapping entitas, migrations, query dengan LINQ dan raw SQL, serta provider database SQL Server, PostgreSQL, dan SQLite.