Dependency Injection (ServiceCollection) în C# (.NET)

Ecosistem .NET: DI, EF Core și teste .NET Core 1.0 (Microsoft.Extensions.DependencyInjection)

Containerul DI din .NET creează obiectele și le dă dependențele prin constructor; înregistrezi serviciile ca Singleton, Scoped sau Transient.

Sintaxă C#
services.AddSingleton<IServiciu, Implementare>();   services.AddScoped<T>();   services.AddTransient<T>();

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&lt;T&gt; 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.GetService peste tot în cod (service locator) în loc de injectare prin constructor ascunde dependențele.
  • Uitarea înregistrării: GetRequiredService aruncă InvalidOperationException cu mesajul că nu există serviciu pentru tipul cerut.

Comenzi înrudite