Dependency Injection – Services Lifetime

.NET dostarcza wbudowane klasy do zarządzania zależnościami, ServiceCollection oraz ServiceProvider.

Klasa ServiceCollection dostarcza metody (głównie extension methods) do rejestracji typów, interesują nas trzy z nich:

builder.Services.AddTransient()
builder.Services.AddScoped()
builder.Services.AddSingleton()

Transient – instancja jest tworzona za każdym razem kiedy jest requestowana.

Scoped – instancja jest tworzona w danym zakresie. W naszym przykładzie używamy Web API, gdzie scope jest realizowany w ramach przetwarzania żądania HTTP.

Singleton – instancja jest tworzona na czas działania aplikacji

Przygotowałem proste klasy testowe, abyśmy mogli zwizualizować, co to oznacza w praktyce. Celowo jest jedna klasa, która implementuje interfejs, oraz klasa zwykła, aby pokazać, że w kontenerze można zarejestrować zarówno jedną, jak i drugą.

public interface IGuidProvider
{
    Guid Id { get; }
}

public class GuidProvider : IGuidProvider
{
    public Guid Id { get; } = Guid.NewGuid();
}

public class GuidProviderProxy(IGuidProvider valueGenerator) // primary constructor 
{
    public Guid Id => valueGenerator.Id;
}
builder.Services.AddTransient<IGuidProvider, GuidProvider>();
builder.Services.AddTransient<GuidProviderProxy>();

builder.Services.AddScoped<IGuidProvider, GuidProvider>();
builder.Services.AddScoped<GuidProviderProxy>();

builder.Services.AddSingleton<IGuidProvider, GuidProvider>();
builder.Services.AddSingleton<GuidProviderProxy>();

Potrzebujemy jeszcze testowego kontrolera. W konstruktorze podajemy zależności, które chcemy wstrzyknąć, a Web API zajmuje się tworzeniem odpowiednich instancji z ServiceProvidera. Możemy wstrzykiwać zarówno interfejsy, jak i klasy!

[ApiController]
[Route("[controller]")]
public class ValueController : Controller
{
    private readonly IGuidProvider _valueGenerator;
    private readonly GuidProviderProxy _proxy;

    public ValueController(
        IGuidProvider valueGenerator,
        GuidProviderProxy proxy)
    {
        _valueGenerator = valueGenerator;
        _proxy = proxy;
    }

    [HttpGet]
    public IActionResult GetValue()
    {
        return Ok(new
        {
            value1 = _valueGenerator.Id,
            value2 = _proxy.Id
        });
    }
}

W Swaggerze albo w przeglądarce łatwo jesteśmy w stanie przetestować wyniki:

Transient – po każdym odświeżeniu dostajemy różnie wartości

Scoped – dostajemy te same wartości dla każdego requesta HTTP

Singleton – Ta sama wartość na czas działania aplikacji (poniżej też jest załączony gif, niestety nie widać odświeżania)

Zwracajcie uwagę na sposób rejestrowania typów — czasami już na wczesnym etapie można uniknąć wielu problemów!

Scroll to Top