HttpClient w .net

Udostępnij

Kolejną rzeczą, która imponuje podczas całej transformacji z .NET Framework do .NET Core i najnowszych wersji .NET, jest fakt, że nie tylko dodawane są nowe funkcjonalności, ale jednocześnie adresowane są problemy ze starymi rozwiązaniami. Bardzo dobrym przykładem jest klasa HttpClient. Typowe użycie w przeszłości wyglądało tak: tworzymy nową instancję, opakowujemy ją w using i ruszamy dalej.

using (var client = new HttpClient())
{
    var response = await client.GetAsync("http://myapi.com");
    // dalsza obsługa
}

Teoretycznie wszystko zgodnie ze sztuką – zawsze powtarzano, że należy zwalniać zasoby, gdy nie są już potrzebne. Używamy using i powinno być dobrze. Niestety, w przypadku HttpClient nie rozwiązuje to wszystkich problemów. Choć zasoby są zwalniane, to otwarte połączenia TCP mogą wciąż pozostawać aktywne. Na szczęście, jak wspomniałem wcześniej, problemy w starych rozwiązaniach zostały zaadresowane!

Rozwiązania problemów z trzymaniem połączeń

Skoro wiemy, jaki jest problem, pora na rozwiązania. Najprostsze z nich to ponowne wykorzystanie tej samej instancji HttpClient. Skoro przy każdej nowej instancji HttpClient tworzone jest również połączenie, korzystanie z jednej wspólnej instancji może pomóc ograniczyć liczbę otwartych gniazd. Oczywiście, nie jest to rozwiązanie idealne.

Kolejnym, bardziej zalecanym podejściem jest użycie IHttpClientFactory. Jest to mechanizm wprowadzony w .NET Core 2.1, który zarządza cyklem życia zarówno HttpClient, jak i utrzymywanych połączeń. Dzięki IHttpClientFactory tworzony jest zoptymalizowany, zarządzany pulą połączeń klient HTTP.

public class ApiService
{
    private readonly IHttpClientFactory _httpClientFactory;

    public ApiService(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task<int> GetCount()
    {
        using var client = _httpClientFactory.CreateClient();
        await client.GetAsync("http://myapi.com");
        // obsługa
    }
}

Named Clients

Dodatkowo, jeśli korzystamy z kontenera zależności, możemy wykorzystać wbudowane metody pomocnicze do rejestrowania klientów HTTP. Dzięki temu jesteśmy w stanie wstępnie skonfigurować każdego klienta, co pozwala na bardziej wygodne i elastyczne zarządzanie ich konfiguracją. Na przykład, możemy dla konkretnego klienta ustawić bazowy adres URL. Dzięki temu w dalszym użyciu wystarczy podać jedynie adres endpointu, co znacznie upraszcza kod – moim zdaniem, jest to mega wygodne. Następnie, przy użyciu metody CreateClient, jako parametr podajemy nazwę klienta, którego chcemy utworzyć.

var services = new ServiceCollection();
services.AddHttpClient("client1", client =>
{
    client.BaseAddress = new Uri("http://myapi.com");
});
services.AddHttpClient("client2");

using var client = _httpClientFactory.CreateClient("client1");

Typed Clients

Named Clients wydają się ciekawym rozwiązaniem, ale mają też swoje wady. Trzeba pamiętać, jak nazwaliśmy klienta, a później w naszym kodzie posługiwać się dokładnie tą samą nazwą. Na szczęście można to zrobić jeszcze wygodniej. Możemy skorzystać z innego rozszerzenia, AddHttpClient, z parametrem generycznym. Dzięki temu możemy zarejestrować naszą klasę, która będzie korzystać z HttpClient. W praktyce nadal będziemy używać Named Client, ale zamiast nazwy możemy posługiwać się typem, co jest znacznie wygodniejsze w użyciu. Dodatkowo, w tym podejściu nie musimy już ręcznie tworzyć klientów – kontener DI zrobi to za nas, korzystając z IHttpClientFactory.

// lekko modyfikujemy kod naszego ApiService
public class ApiService
{
    private readonly HttpClient _httpClient;

    public ApiService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<int> GetCount()
    {
        await _httpClient.GetAsync("http://myapi.com");
        // obsługa
    }
}

// przy generycznej wersji również możemy skonfigurować http client. 
services.AddHttpClient<ApiService>(client =>
{
    client.BaseAddress = new Uri("http://myapi.com");
});

Podsumowanie

Gdy już poznamy zmiany, jakie zostały wprowadzone, tworzenie klientów HTTP w nowy sposób szybko wchodzi w nawyk. Dodatkowo metody rejestrujące HttpClient są znacznie wygodniejsze w użyciu i pozwalają na większą elastyczność oraz efektywne zarządzanie konfiguracją w jednym miejscu. Mając przy tym świadomość, że jest to dobre podejście, które minimalizuje ryzyko niewłaściwego zarządzania zasobami, dla mnie wybór jest oczywisty!

Czytaj również  Dependency Injection - Services Lifetime
Scroll to Top