Scoped ServiceProvider – Własna implementacja

Udostępnij

Wstęp

W poprzednim wpisie dotyczącym service lifetimes, na przykładzie Web API, opisałem jak są tworzone zależności typu Scoped – w kontekście requestów HTTP. A co, jeśli nie używamy HTTP? Otóż można wprowadzić własną implementację Scope, w zależności od tego, jak nasza biblioteka/aplikacja go definiuje. Do tego posłużą nam klasy i metody dostarczone przez IServiceCollection oraz IServiceProvider.

Prosty przykład

Aby utworzyć Scope, potrzebujemy dostępu do instancji IServiceProvider, a następnie wywołania metody CreateScope – prościej się nie da! Utworzyłem w tym celu klasę pomocniczą, która wykonuje cały kod. Nazwałem ją Processor, a metoda (oczywiście!) nosi nazwę Process.

public interface IGuidProvider
{
    Guid Id { get; }
}

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

public class Processor
{
    private readonly IServiceProvider _serviceProvider;

    public Processor(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void Process()
    {
        using var scope = _serviceProvider.CreateScope();
        var guidProvider = scope.ServiceProvider.GetRequiredService<IGuidProvider>();
        Console.WriteLine(guidProvider.Id);
    }
}

Poniżej znajduje się cały setup w Program.cs. Po uruchomieniu kodu na ekranie widzimy dwa różne Guidy, co sugeruje, że działa poprawnie.

public class Program
{
    public static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddSingleton<Processor>();
        serviceCollection.AddScoped<IGuidProvider, GuidProvider>();
        var provider = serviceCollection.BuildServiceProvider();
        var processor = provider.GetRequiredService<Processor>();
        processor.Process();
        processor.Process();
    }
}

Haczyk!

A co się stanie, jeśli pokusimy się o użycie oryginalnego IServiceProvider zamiast Scoped? Jeśli ktoś już próbował czegoś takiego w Web API, wie, że może to skutkować błędem: „Cannot resolve scoped service ” from root provider”. Dlatego wiemy, że nigdy nie powinniśmy tego robić! Ale co jeśli?! Otóż, NIC. Aplikacja się uruchamia, co prawda zwraca dwa identyczne Guidy, ale nie pojawi się wyjątek.

Aby mieć pewność, że typy będą poprawnie sprawdzane, podczas tworzenia ServiceProvider mamy możliwość ustawienia flagi ValidateScopes, która wykona odpowiednią walidację przy próbie wyciągnięcia zależności. Po ustawieniu tej flagi możemy zobaczyć znany błąd:
„Cannot resolve scoped service 'IGuidProvider’ from root provider.”. W przypadku flagi ustawionej na false, domyślnie są tworzone zależności typu Singleton.

public void Process()
{
    var guidProvider = _serviceProvider.GetRequiredService<IGuidProvider>(); // root service provider
    Console.WriteLine(guidProvider.Id);
}

var provider = serviceCollection.BuildServiceProvider(validateScopes: true); 

Czytaj również  Xamarin.Forms - Content View
Scroll to Top