Как правильно создать и использовать HttpClient в .NET?

Ответ

Правильное создание HttpClient критически важно для производительности и надежности приложения. Прямое создание экземпляра через new HttpClient() в долгоживущих сервисах ведет к исчерпанию сокетов. Рекомендуемый подход — использование IHttpClientFactory.

1. Предпочтительный способ: IHttpClientFactory (ASP.NET Core)

Регистрация в DI-контейнере (Program.cs):

builder.Services.AddHttpClient(); // Базовая регистрация
// Или с именованным клиентом и настройками:
builder.Services.AddHttpClient("GitHubClient", client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.Add("User-Agent", "MyApp");
});

Использование в сервисе:

public class MyApiService
{
    private readonly HttpClient _httpClient;
    // Инжектируем фабрику
    public MyApiService(IHttpClientFactory httpClientFactory)
    {
        // Создаем клиент. Его жизненный цикл управляется фабрикой.
        _httpClient = httpClientFactory.CreateClient();
        // Или используем именованного клиента:
        // _httpClient = httpClientFactory.CreateClient("GitHubClient");
    }

    public async Task<string> GetDataAsync()
    {
        try
        {
            var response = await _httpClient.GetStringAsync("/users/octocat/repos");
            return response;
        }
        catch (HttpRequestException e)
        {
            // Логирование ошибки
            Console.WriteLine($"Request error: {e.Message}");
            throw;
        }
    }
}

2. Использование для разовых запросов Если клиент не инжектируется (например, в консольном приложении), используйте фабрику для создания и последующей правильной утилизации:

var services = new ServiceCollection();
services.AddHttpClient();
var serviceProvider = services.BuildServiceProvider();

var factory = serviceProvider.GetRequiredService<IHttpClientFactory>();
var client = factory.CreateClient();
// ... использовать client
// Не нужно вызывать Dispose() вручную — фабрика управляет этим.

Преимущества IHttpClientFactory:

  • Управление пулом соединений: Избегает проблемы исчерпания сокетов ("Socket exhaustion").
  • Централизованная конфигурация: Настройки базового адреса, таймаутов, заголовков задаются в одном месте.
  • Интеграция с Polly: Легко добавить политики повтора, размыкателя цепи.
  • Внедрение зависимостей: Клиенты легко тестируются и конфигурируются.

Ответ 18+ 🔞

Слушай, а вот этот твой HttpClient — это же просто какая-то мина замедленного действия, если его неправильно юзать! Ну реально, народ как делает? new HttpClient() в коде, и думают, что они красавчики. А потом приложение через час работы накрывается медным тазом, сокеты все исчерпаны, и начинается: "ой, а что это у меня запросы виснут, блядь?".

Так вот, запомни раз и навсегда: в долгоживущих сервисах (типа веб-апишек) НИ В КОЕМ СЛУЧАЕ не создавай клиент напрямую через new. Это пиздец как ресурсоёмко. Твой сервер упрётся в лимит сокетов, и будет тебе хиросима, а не работающее приложение.

Правильный путь — это IHttpClientFactory (в ASP.NET Core). Вот как это делается, без всякой хуйни.

1. Регистрируем эту фабрику в своём приложении (Program.cs):

builder.Services.AddHttpClient(); // Самый простой вариант, базовая регистрация.
// А если хочешь по-взрослому, с именем и настройками:
builder.Services.AddHttpClient("GitHubClient", client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.Add("User-Agent", "MyApp"); // А то GitHub тебя посылать нахуй начнёт без заголовка
});

2. Используем в своём сервисе:

Смотри, как красиво и без напряга.

public class MyApiService
{
    private readonly HttpClient _httpClient;

    // Просто просим фабрику у системы, и она нам даёт
    public MyApiService(IHttpClientFactory httpClientFactory)
    {
        // Берём обычного, безымянного
        _httpClient = httpClientFactory.CreateClient();
        // Или вот так — берём конкретного, которого "GitHubClient" назвали
        // _httpClient = httpClientFactory.CreateClient("GitHubClient");
    }

    public async Task<string> GetDataAsync()
    {
        try
        {
            // И работаем спокойно, зная, что сокеты не кончатся
            var response = await _httpClient.GetStringAsync("/users/octocat/repos");
            return response;
        }
        catch (HttpRequestException e)
        {
            // Ну тут логируй ошибку, не будь мудаком
            Console.WriteLine($"Request error: {e.Message}");
            throw;
        }
    }
}

3. А если у меня консольное приложение, и DI-контейнера как бы нет?

И тут не надо выёбываться с прямым созданием. Сделай мини-контейнер, ёпта!

var services = new ServiceCollection();
services.AddHttpClient(); // Регаем фабрику
var serviceProvider = services.BuildServiceProvider();

// Достаём фабрику
var factory = serviceProvider.GetRequiredService<IHttpClientFactory>();
// Создаём клиента
var client = factory.CreateClient();
// ... используешь client для запросов
// И ГЛАВНОЕ — НЕ ВЫЗЫВАЙ client.Dispose() РУКАМИ! Фабрика сама всё порешает.

И в чём, собственно, охуенность этого подхода?

  • Пул соединений на уровне: Больше не будет проблемы "Socket exhaustion", потому что соединения переиспользуются, а не создаются для каждого запроса как у дебила.
  • Всё настроил в одном месте: Базовый адрес, таймауты, заголовки — задал при регистрации и забыл. Не нужно эту хуйню растаскивать по всему коду.
  • Легко прикрутить Polly: Хочешь автоматические повторы при ошибках или circuit breaker? За две строки настраивается через эту же фабрику. Красота!
  • Тестируемость: Клиенты инжектятся, их легко замокать в юнит-тестах. Ты же пишешь тесты, да? А то я волнение ебать чувствую...

Короче, выбрось из головы new HttpClient() для постоянной работы. Бери фабрику и живи спокойно.