Dependency Injection — AddSingleton, AddScoped, AddTransient, nima farqi

~25 daq · .NET Backend · ASP.NET Core — Kirish

Muammodan boshlaylik

public class BuyurtmaController : ControllerBase
{
    private readonly BuyurtmaService _service = new BuyurtmaService(
        new BuyurtmaRepository(new DokonDbContext("Host=...;Password=...")),
        new EmailYuboruvchi(new SmtpClient("smtp.mail.uz")),
        new Logger());
}

Muammolar:

  • Controller o'zi barcha bog'liqliklarni yaratadi va ularning bog'liqliklarini ham — butun zanjirni biladi
  • Connection string kodda; email ni Telegram ga almashtirish — controller ni o'zgartirish
  • Test qilib bo'lmaydi — haqiqiy baza va haqiqiy email
  • Har so'rovda yangi baza ulanishi yoki aksincha — hamma uchun bitta, nazoratsiz

Dependency Injection nima?

DI — klass o'z bog'liqliklarini o'zi yaratmaydi, ularni tashqaridan (odatda konstruktor orqali) oladi:

public class BuyurtmaController(IBuyurtmaService service) : ControllerBase
{
    [HttpPost]
    public async Task<IActionResult> Yarat(BuyurtmaDto dto) => Ok(await service.YaratAsync(dto));
}

public class BuyurtmaService(IBuyurtmaRepository repo, IXabarYuboruvchi xabar, ILogger<BuyurtmaService> logger)
    : IBuyurtmaService
{
    // ...
}

Kim yaratadi? ASP.NET Core ning DI konteyneri. Siz faqat Program.cs da "qaysi interfeys uchun qaysi klass" ekanini aytasiz:

builder.Services.AddScoped<IBuyurtmaService, BuyurtmaService>();
builder.Services.AddScoped<IBuyurtmaRepository, BuyurtmaRepository>();
builder.Services.AddScoped<IXabarYuboruvchi, EmailYuboruvchi>();

So'rov kelganda konteyner BuyurtmaController ni yaratadi → unga IBuyurtmaService kerak → BuyurtmaService ni yaratadi → unga repo, xabar, logger kerak → ularni ham yaratadi. Butun zanjir — avtomatik.


Hayotiy o'xshatma

Restoran oshpazi. U o'zi bozorga borib go'sht sotib olmaydi, pichoqni o'zi charxlamaydi — har ertalab kerakli masalliq va asboblar uning stoliga yetkazib beriladi. Oshpaz faqat "menga go'sht va o'tkir pichoq kerak" deydi (konstruktor). Ta'minotchini almashtirish — oshpazning ishiga ta'sir qilmaydi.


Uch xil umr (lifetime)

builder.Services.AddSingleton<IKesh, XotiraKeshi>();
builder.Services.AddScoped<IBuyurtmaService, BuyurtmaService>();
builder.Services.AddTransient<IParolGenerator, ParolGenerator>();
Lifetime Nechta nusxa Qachon
Singleton Butun ilova uchun bitta Holatsiz yoki thread-safe: kesh, sozlamalar, HttpClient fabrikasi
Scoped Har bir HTTP so'rov uchun bitta Ko'pchilik servislar: DbContext, repository lar, biznes servislar
Transient Har so'ralganda yangi Yengil, holatsiz yordamchilar
So'rov 1:  Controller → ServiceA(scoped #1) → Repo(scoped #1)
                      → ServiceB(scoped #1) → Repo(scoped #1)   ← bir xil nusxa
So'rov 2:  Controller → ServiceA(scoped #2) → Repo(scoped #2)   ← yangi nusxa
Singleton:  hamma so'rovlarda bitta

Ko'rish uchun tajriba

public class Hisoblagich { public Guid Id { get; } = Guid.NewGuid(); }

builder.Services.AddScoped<Hisoblagich>();

app.MapGet("/test", (Hisoblagich a, Hisoblagich b) => new { a = a.Id, b = b.Id, bir_xil = a.Id == b.Id });
// Scoped: bitta so'rovda bir_xil = true, keyingi so'rovda — yangi Id
// Transient: bir_xil = false
// Singleton: doim bir xil Id

Captive dependency — xavfli xato

[!WARNING] Singleton servis Scoped servisni konstruktorda olmasligi kerak. Singleton bir marta yaratiladi va scoped servis (masalan, DbContext) uning ichida abadiy qolib ketadi — barcha so'rovlar bitta DbContext ni bir vaqtda ishlatadi: eskirgan ma'lumot, xatolar va thread-safety muammolari. Development da ASP.NET Core buni aniqlaydi: InvalidOperationException: Cannot consume scoped service 'DokonDbContext' from singleton 'X'.

Qoida: servis faqat o'zidan uzoqroq yoki teng umrli servislarga bog'lansin:

Kim ← kimni oladi Singleton Scoped Transient
Singleton ✅ ❌ ⚠️
Scoped ✅ ✅ ✅
Transient ✅ ✅ ✅

Singleton ichida scoped servis kerak bo'lsa — IServiceScopeFactory bilan qisqa scope yaratish (masalan, fon vazifalarida).


Ro'yxatdan o'tkazish usullari

builder.Services.AddScoped<IMahsulotService, MahsulotService>();   // interfeys → klass
builder.Services.AddScoped<MahsulotService>();                     // klassning o'zi
builder.Services.AddSingleton<ISozlama>(new Sozlama { Til = "uz" });   // tayyor nusxa
builder.Services.AddScoped<IXabar>(sp =>                           // fabrika
{
    var config = sp.GetRequiredService<IConfiguration>();
    return config["Xabar:Kanal"] == "telegram"
        ? new TelegramYuboruvchi()
        : new EmailYuboruvchi();
});

Framework servislari — tayyor

ILogger<T>, IConfiguration, IWebHostEnvironment, IHttpClientFactory, IMemoryCache — konstruktorga yozsangiz bas.


Qayerda olish mumkin

// konstruktor — asosiy usul
public class MahsulotService(DokonDbContext db, ILogger<MahsulotService> logger) { }

// controller action parametri
public IActionResult Ol([FromServices] IKesh kesh) => Ok();

// Minimal API — avtomatik
app.MapGet("/x", (IMahsulotService s) => s.Hammasi());
// ❌ Service Locator — bog'liqliklar yashirin, test qilish qiyin
var s = HttpContext.RequestServices.GetService<IMahsulotService>();

DI ning foydasi

DI siz DI bilan
Bog'liqlikni almashtirish Kodni o'zgartirish Program.cs dagi bitta qator
Test Haqiqiy baza va email Mock/soxta implementatsiya (77-dars)
Umrni boshqarish Qo'lda Konteyner — Singleton/Scoped/Transient
Resurslarni yopish Qo'lda Dispose Konteyner scope tugaganda o'zi

[!TIP] Konstruktorda 5–6 dan ko'p bog'liqlik bo'lsa — klass juda ko'p ish qilyapti. Uni kichikroq servislarga bo'lish haqida o'ylang.


Tez-tez uchraydigan xatolar

Ro'yxatdan o'tkazilmagan servis:

InvalidOperationException: Unable to resolve service for type 'IMahsulotService' while attempting to activate 'MahsulotlarController'.

builder.Services.AddScoped<IMahsulotService, MahsulotService>() ni unutgansiz.

Singleton ichida DbContext — captive dependency.

Servis ichida new bilan bog'liqlik yaratish — DI ning foydasi yo'qoladi.


Xulosa

DI — klass bog'liqliklarini o'zi yaratmaydi, konstruktor orqali oladi; ASP.NET Core konteyneri butun zanjirni avtomatik yaratadi. Ro'yxatdan o'tkazish — Program.cs da: builder.Services.AddScoped<IInterfeys, Klass>().

Lifetime: Singleton — ilova uchun bitta, Scoped — har so'rovga bitta (DbContext va ko'pchilik servislar), Transient — har safar yangi. Singleton scoped servisni olmasin (captive dependency). Bog'liqliklarni konstruktor orqali oling, service locator emas. Natija — almashtirish va test qilish oson.


Keyingi dars

39-dars: Middleware — nima, pipeline, tartib, custom middleware yaratish