HTTP клиент на C#: делаем запросы правильно и эффективно

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: зачем нужен правильный HTTP клиент на C#?

В современной разработке практически ни одно приложение не обходится без взаимодействия с внешними сервисами. Будь то отправка данных на сервер, получение курса валют или работа с облачным API — всё это требует грамотной реализации HTTP-запросов. В экосистеме C# (.NET) основным инструментом для этой задачи является класс HttpClient. Однако многие разработчики, особенно начинающие, допускают типичные ошибки: создают новый экземпляр HttpClient на каждый запрос, забывают обрабатывать исключения или неправильно работают с асинхронностью.

В этой статье мы разберём, как правильно организовать HTTP-клиент на C#, рассмотрим отправку GET и POST запросов, работу с заголовками и телом запроса, а также лучшие практики обработки ошибок. После прочтения вы сможете писать надёжный и производительный код для взаимодействия с любыми REST API.

Основы работы с HttpClient в C#

Класс HttpClient входит в пространство имён System.Net.Http и доступен во всех современных версиях .NET (Core, 5, 6, 7, 8 и выше). Его главное преимущество — поддержка асинхронных операций и автоматическое управление пулом подключений.

Правильное создание HttpClient

Важнейшее правило: не создавайте новый HttpClient на каждый запрос. Это может привести к исчерпанию сокетов (socket exhaustion). Вместо этого используйте один экземпляр на всё время жизни приложения или применяйте IHttpClientFactory.

// ❌ Плохо: новый HttpClient на каждый запрос
using (var client = new HttpClient())
{
    var response = await client.GetAsync("https://api.example.com");
}

// ✅ Хорошо: один HttpClient для всех запросов
public class MyHttpService
{
    private static readonly HttpClient _client = new HttpClient();
    
    public async Task<string> GetDataAsync(string url)
    {
        return await _client.GetStringAsync(url);
    }
}

Базовые настройки

При инициализации HttpClient можно задать базовый адрес, таймауты и заголовки по умолчанию:

public class ApiClient
{
    private readonly HttpClient _httpClient;
    
    public ApiClient()
    {
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri("https://api.pythonlib.ru/"),
            Timeout = TimeSpan.FromSeconds(10)
        };
        
        // Заголовки, которые будут отправляться с каждым запросом
        _httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
        _httpClient.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
    }
}

Отправка GET и POST запросов

Рассмотрим два самых распространённых типа HTTP-запросов: получение данных (GET) и отправка данных (POST).

GET-запрос: получение данных

GET-запрос используется для чтения данных с сервера. Он не должен изменять состояние ресурса и не содержит тела.

public async Task<string> GetUserAsync(int userId)
{
    try
    {
        var response = await _httpClient.GetAsync($"/api/users/{userId}");
        response.EnsureSuccessStatusCode(); // Выбросит исключение при ошибке
        return await response.Content.ReadAsStringAsync();
    }
    catch (HttpRequestException ex)
    {
        Console.WriteLine($"Ошибка запроса: {ex.Message}");
        throw;
    }
}

Для простых случаев можно использовать сокращённую форму:

string json = await _httpClient.GetStringAsync("/api/users/42");

POST-запрос: отправка данных

POST-запрос применяется для создания новых ресурсов. Данные передаются в теле запроса, чаще всего в формате JSON.

public async Task<User> CreateUserAsync(User newUser)
{
    // Сериализуем объект в JSON
    var json = JsonSerializer.Serialize(newUser);
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    
    var response = await _httpClient.PostAsync("/api/users", content);
    response.EnsureSuccessStatusCode();
    
    // Десериализуем ответ
    var responseBody = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<User>(responseBody);
}

Работа с заголовками и параметрами

Иногда требуется передать токен авторизации или дополнительные параметры:

public async Task<string> GetWithAuthAsync(string url, string token)
{
    var request = new HttpRequestMessage(HttpMethod.Get, url);
    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
    
    var response = await _httpClient.SendAsync(request);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsStringAsync();
}

Обработка ошибок и лучшие практики

Даже идеально написанный код может столкнуться с сетевыми проблемами или ошибками сервера. Важно правильно обрабатывать такие ситуации.

Классы HTTP-статусов

  • 2xx (Успех): 200 OK, 201 Created, 204 No Content
  • 3xx (Перенаправление): 301 Moved Permanently, 302 Found
  • 4xx (Ошибка клиента): 400 Bad Request, 401 Unauthorized, 404 Not Found
  • 5xx (Ошибка сервера): 500 Internal Server Error, 502 Bad Gateway

Обработка исключений и повторные попытки

Для надёжности используйте политику повторных попыток (retry policy):

public async Task<string> GetWithRetryAsync(string url, int maxRetries = 3)
{
    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            var response = await _httpClient.GetAsync(url);
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
        catch (HttpRequestException) when (attempt < maxRetries)
        {
            Console.WriteLine($"Попытка {attempt} не удалась, повтор через 2 секунды...");
            await Task.Delay(2000);
        }
    }
    throw new Exception("Все попытки запроса исчерпаны");
}

Управление временем жизни HttpClient

В ASP.NET Core рекомендуется использовать IHttpClientFactory:

// В Program.cs или Startup.cs
builder.Services.AddHttpClient("ApiClient", client =>
{
    client.BaseAddress = new Uri("https://api.pythonlib.ru/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
});

// В сервисе
public class MyService
{
    private readonly IHttpClientFactory _factory;
    
    public MyService(IHttpClientFactory factory) => _factory = factory;
    
    public async Task<string> GetDataAsync()
    {
        var client = _factory.Create                

Блоги

Рекомендаций по книгам