Ce face Dependency Injection (ServiceCollection)
Cu dependency injection, o clasă nu își creează singură dependențele cu new, ci le cere în constructor, de obicei ca interfețe. Containerul (IServiceProvider) știe ce implementare corespunde fiecărei interfețe și construiește tot lanțul. Așa poți înlocui o implementare (de exemplu un serviciu de e-mail real cu unul de test) fără să schimbi clasele care o folosesc.
Durata de viață se alege la înregistrare: AddSingleton creează o singură instanță pentru toată aplicația, AddScoped una pentru fiecare scope (în ASP.NET Core, pentru fiecare cerere HTTP), iar AddTransient una nouă la fiecare cerere a serviciului. DbContext din EF Core se înregistrează implicit ca Scoped.
În ASP.NET Core și în aplicațiile cu Host, colecția este deja creată: înregistrezi pe builder.Services, iar framework-ul rezolvă serviciile pentru controllere și endpoint-uri. Într-o aplicație de consolă simplă adaugi pachetul Microsoft.Extensions.DependencyInjection.
Parametri
| Parametru | Descriere |
|---|---|
AddSingleton |
O instanță pentru toată viața aplicației. |
AddScoped |
O instanță per scope (per cerere HTTP în ASP.NET Core). |
AddTransient |
O instanță nouă la fiecare rezolvare. |
GetRequiredService<T> |
Cere serviciul; aruncă InvalidOperationException dacă nu e înregistrat. |
Exemple
Exemplul 1: Interfață, implementare și injectare prin constructor
Convertor nu știe ce curs folosește; containerul îi dă implementarea înregistrată (aici un curs fix, pentru exemplu).
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddSingleton<ICursValutar, CursFix>();
services.AddTransient<Convertor>();
using var provider = services.BuildServiceProvider();
var convertor = provider.GetRequiredService<Convertor>();
Console.WriteLine(convertor.InLei(100));
interface ICursValutar
{
int LeiPeEuro { get; }
}
class CursFix : ICursValutar
{
public int LeiPeEuro => 5;
}
class Convertor(ICursValutar curs)
{
public string InLei(int euro) => $"{euro} EUR = {euro * curs.LeiPeEuro} lei";
}
Rezultat
100 EUR = 500 lei
Exemplul 2: Singleton, Scoped și Transient
Fiecare clasă își numără instanțele. Configurarea e aceeași peste tot, coșul se repetă doar în aceeași cerere, jurnalul e nou de fiecare dată.
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddSingleton<Configurare>();
services.AddScoped<CosCumparaturi>();
services.AddTransient<Jurnal>();
using var provider = services.BuildServiceProvider();
for (int cerere = 1; cerere <= 2; cerere++)
{
using var scope = provider.CreateScope();
var sp = scope.ServiceProvider;
Console.WriteLine($"Cererea {cerere}: " +
$"config #{sp.GetRequiredService<Configurare>().Nr}, " +
$"coș #{sp.GetRequiredService<CosCumparaturi>().Nr} și #{sp.GetRequiredService<CosCumparaturi>().Nr}, " +
$"jurnal #{sp.GetRequiredService<Jurnal>().Nr} și #{sp.GetRequiredService<Jurnal>().Nr}");
}
class Configurare { static int _total; public int Nr { get; } = ++_total; }
class CosCumparaturi { static int _total; public int Nr { get; } = ++_total; }
class Jurnal { static int _total; public int Nr { get; } = ++_total; }
Rezultat
Cererea 1: config #1, coș #1 și #1, jurnal #1 și #2
Cererea 2: config #1, coș #2 și #2, jurnal #3 și #4
Greșeli frecvente
- Un serviciu Singleton care primește în constructor un serviciu Scoped (de exemplu un
DbContext): instanța Scoped rămâne prinsă pentru toată aplicația. În mediul Development, ASP.NET Core aruncă excepție la pornire pentru asta. - Apelarea
provider.GetServicepeste tot în cod (service locator) în loc de injectare prin constructor ascunde dependențele. - Uitarea înregistrării:
GetRequiredServicearuncăInvalidOperationExceptioncu mesajul că nu există serviciu pentru tipul cerut.
Comenzi înrudite
Entity Framework Core: DbContext și DbSet: DbContext este legătura cu baza de date în EF Core: DbSet<T> reprezintă un tabel, iar SaveChanges salvează adăugările, modificările și ștergerile.Teste unitare cu xUnit ([Fact], [Theory]): xUnit este un framework de teste pentru .NET: [Fact] marchează un test, [Theory] cu [InlineData] rulează același test pe mai multe date.