HttpClient
.NETフレームワークに組み込まれた標準HTTPクライアント。HttpClientFactoryによる依存性注入とライフサイクル管理をサポート。非同期通信、HTTP/2対応、自動圧縮、詳細なタイムアウト設定機能を提供。エンタープライズアプリケーションで推奨される標準実装。
GitHub概要
dotnet/runtime
.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
スター17,028
ウォッチ458
フォーク5,203
作成日:2019年9月24日
言語:C#
ライセンス:MIT License
トピックス
dotnethacktoberfesthelp-wanted
スター履歴
データ取得日時: 2025/10/22 10:03
ライブラリ
HttpClient
概要
HttpClientは、.NET標準のHTTPクライアントライブラリです。.NET Framework 4.5以降および.NET Core/.NET 5以降で利用可能で、非同期プログラミング(async/await)を完全サポートし、IHttpClientFactoryとの統合により高パフォーマンスとリソース効率を実現します。2025年においても、.NET開発者にとって最も推奨されるHTTPクライアントソリューションです。
詳細
HttpClientは.NET Frameworkの初期バージョンからWebRequestの後継として開発され、現代的なHTTP通信のニーズに対応しています。特に.NET Core/.NET 5以降では、IHttpClientFactoryとの統合により、ソケット枯渇問題やDNS変更問題が解決され、エンタープライズレベルのアプリケーションでも安心して使用できます。System.Text.Jsonとの統合やPollyとの連携により、堅牢なHTTP通信が可能です。
主な特徴
- 非同期処理: async/awaitパターンの完全サポート
- IHttpClientFactory: 依存性注入とライフサイクル管理
- 高パフォーマンス: 接続プールとソケット再利用
- JSON統合: System.Text.Jsonによる効率的なJSON処理
- 認証サポート: Bearer Token、JWT、カスタム認証対応
- エラーハンドリング: 包括的な例外処理機能
- HTTP/2対応: 最新プロトコルサポート
- リトライポリシー: Pollyライブラリとの連携
- タイムアウト制御: 詳細なタイムアウト設定
- ヘッダー管理: カスタムヘッダーとユーザーエージェント設定
メリット・デメリット
メリット
- .NET標準ライブラリで追加依存関係が不要
- IHttpClientFactoryによる最適化されたリソース管理
- async/await対応による優れた非同期処理
- 依存性注入(DI)との優れた統合
- Microsoft公式サポートによる信頼性と継続的更新
- System.Text.Jsonとの統合による高速JSON処理
デメリット
- 旧バージョン(.NET Framework 4.x未満)では利用不可
- 基本的な使用法でもある程度のボイラープレートコードが必要
- 高度な機能(リトライ、サーキットブレーカーなど)には外部ライブラリが必要
- 同期処理メソッドは非推奨(デッドロック回避のため)
- 単体テストでのモック化がやや複雑
参考ページ
- HttpClient クラス - Microsoft Docs
- IHttpClientFactory - Microsoft Docs
- HttpClient のベスト プラクティス
- .NET での HTTP 要求の作成
書き方の例
基本的なセットアップ(IHttpClientFactory)
// Program.cs (.NET 6以降)
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
// IHttpClientFactoryの登録
builder.Services.AddHttpClient();
// 名前付きHttpClientの登録
builder.Services.AddHttpClient("ApiClient", client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
client.Timeout = TimeSpan.FromSeconds(30);
});
// 型付きHttpClientの登録
builder.Services.AddHttpClient<ApiService>();
var app = builder.Build();
// サービスクラス例
public class ApiService
{
private readonly HttpClient _httpClient;
public ApiService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("https://api.example.com/");
}
}
基本的なリクエスト
public class HttpClientService
{
private readonly HttpClient _httpClient;
public HttpClientService(HttpClient httpClient)
{
_httpClient = httpClient;
}
// GETリクエスト
public async Task<string> GetDataAsync()
{
try
{
var response = await _httpClient.GetAsync("https://api.example.com/data");
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Request error: {ex.Message}");
throw;
}
}
// POSTリクエスト
public async Task<string> PostDataAsync(object data)
{
var json = JsonSerializer.Serialize(data);
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("https://api.example.com/users", content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
// PUTリクエスト
public async Task<bool> UpdateDataAsync(int id, object data)
{
var json = JsonSerializer.Serialize(data);
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
var response = await _httpClient.PutAsync($"https://api.example.com/users/{id}", content);
return response.IsSuccessStatusCode;
}
// DELETEリクエスト
public async Task<bool> DeleteDataAsync(int id)
{
var response = await _httpClient.DeleteAsync($"https://api.example.com/users/{id}");
return response.IsSuccessStatusCode;
}
}
認証処理
public class AuthenticatedHttpService
{
private readonly HttpClient _httpClient;
public AuthenticatedHttpService(HttpClient httpClient)
{
_httpClient = httpClient;
}
// Bearer Token認証
public void SetBearerToken(string token)
{
_httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
// Basic認証
public void SetBasicAuth(string username, string password)
{
var credentials = Convert.ToBase64String(
System.Text.Encoding.ASCII.GetBytes($"{username}:{password}"));
_httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentials);
}
// JWT Token取得とセット
public async Task<string> LoginAndGetTokenAsync(string username, string password)
{
var loginData = new { Username = username, Password = password };
var json = JsonSerializer.Serialize(loginData);
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("/auth/login", content);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var tokenResponse = JsonSerializer.Deserialize<TokenResponse>(responseContent);
// Tokenをヘッダーに設定
SetBearerToken(tokenResponse.AccessToken);
return tokenResponse.AccessToken;
}
// カスタム認証ヘッダー
public async Task<string> GetWithCustomAuthAsync(string apiKey)
{
using var request = new HttpRequestMessage(HttpMethod.Get, "/api/secure-data");
request.Headers.Add("X-API-Key", apiKey);
request.Headers.Add("X-Client-Version", "1.0");
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public class TokenResponse
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public int ExpiresIn { get; set; }
}
JSON処理(System.Text.Json統合)
public class JsonHttpService
{
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
public JsonHttpService(HttpClient httpClient)
{
_httpClient = httpClient;
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
PropertyNameCaseInsensitive = true
};
}
// オブジェクトの送信
public async Task<TResponse> PostJsonAsync<TRequest, TResponse>(string endpoint, TRequest data)
{
var json = JsonSerializer.Serialize(data, _jsonOptions);
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(endpoint, content);
response.EnsureSuccessStatusCode();
var responseJson = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<TResponse>(responseJson, _jsonOptions);
}
// JSONレスポンスの取得
public async Task<T> GetJsonAsync<T>(string endpoint)
{
var response = await _httpClient.GetAsync(endpoint);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(json, _jsonOptions);
}
// ストリーミングJSON処理(大きなレスポンス用)
public async Task<T> GetJsonStreamAsync<T>(string endpoint)
{
var response = await _httpClient.GetAsync(endpoint);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync<T>(stream, _jsonOptions);
}
// 実用例
public async Task<User> CreateUserAsync(CreateUserRequest request)
{
return await PostJsonAsync<CreateUserRequest, User>("/api/users", request);
}
public async Task<List<User>> GetUsersAsync()
{
return await GetJsonAsync<List<User>>("/api/users");
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public DateTime CreatedAt { get; set; }
}
public class CreateUserRequest
{
public string Name { get; set; }
public string Email { get; set; }
}
エラーハンドリング
public class RobustHttpService
{
private readonly HttpClient _httpClient;
private readonly ILogger<RobustHttpService> _logger;
public RobustHttpService(HttpClient httpClient, ILogger<RobustHttpService> logger)
{
_httpClient = httpClient;
_logger = logger;
}
public async Task<ApiResult<T>> SafeGetAsync<T>(string endpoint)
{
try
{
using var response = await _httpClient.GetAsync(endpoint);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<T>(json);
return ApiResult<T>.Success(data);
}
// HTTPステータスコード別の処理
return response.StatusCode switch
{
HttpStatusCode.NotFound => ApiResult<T>.Error("リソースが見つかりません"),
HttpStatusCode.Unauthorized => ApiResult<T>.Error("認証が必要です"),
HttpStatusCode.Forbidden => ApiResult<T>.Error("アクセスが拒否されました"),
HttpStatusCode.BadRequest => ApiResult<T>.Error("リクエストが無効です"),
HttpStatusCode.InternalServerError => ApiResult<T>.Error("サーバーエラーが発生しました"),
_ => ApiResult<T>.Error($"予期しないエラー: {response.StatusCode}")
};
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "HTTP request failed for endpoint: {Endpoint}", endpoint);
return ApiResult<T>.Error($"ネットワークエラー: {ex.Message}");
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
_logger.LogWarning("Request timeout for endpoint: {Endpoint}", endpoint);
return ApiResult<T>.Error("リクエストがタイムアウトしました");
}
catch (TaskCanceledException ex)
{
_logger.LogWarning("Request cancelled for endpoint: {Endpoint}", endpoint);
return ApiResult<T>.Error("リクエストがキャンセルされました");
}
catch (JsonException ex)
{
_logger.LogError(ex, "JSON deserialization failed for endpoint: {Endpoint}", endpoint);
return ApiResult<T>.Error("レスポンスの解析に失敗しました");
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error for endpoint: {Endpoint}", endpoint);
return ApiResult<T>.Error("予期しないエラーが発生しました");
}
}
// リトライ処理付きリクエスト
public async Task<T> GetWithRetryAsync<T>(string endpoint, int maxRetries = 3)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
var response = await _httpClient.GetAsync(endpoint);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(json);
}
catch (HttpRequestException ex) when (attempt < maxRetries)
{
_logger.LogWarning("Attempt {Attempt} failed, retrying... Error: {Error}",
attempt, ex.Message);
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); // 指数バックオフ
}
}
throw new Exception($"Failed after {maxRetries} attempts");
}
}
public class ApiResult<T>
{
public bool IsSuccess { get; set; }
public T Data { get; set; }
public string ErrorMessage { get; set; }
public static ApiResult<T> Success(T data) => new() { IsSuccess = true, Data = data };
public static ApiResult<T> Error(string message) => new() { IsSuccess = false, ErrorMessage = message };
}
非同期処理とパフォーマンス最適化
public class PerformantHttpService
{
private readonly HttpClient _httpClient;
private readonly SemaphoreSlim _semaphore;
public PerformantHttpService(HttpClient httpClient)
{
_httpClient = httpClient;
_semaphore = new SemaphoreSlim(10, 10); // 同時実行数を制限
}
// 複数URL並行取得
public async Task<Dictionary<string, string>> GetMultipleUrlsAsync(IEnumerable<string> urls)
{
var tasks = urls.Select(async url =>
{
await _semaphore.WaitAsync();
try
{
var response = await _httpClient.GetStringAsync(url);
return new KeyValuePair<string, string>(url, response);
}
finally
{
_semaphore.Release();
}
});
var results = await Task.WhenAll(tasks);
return results.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
// CancellationToken活用
public async Task<string> GetWithCancellationAsync(string endpoint, CancellationToken cancellationToken = default)
{
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken, timeoutCts.Token);
try
{
var response = await _httpClient.GetAsync(endpoint, combinedCts.Token);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
catch (OperationCanceledException) when (timeoutCts.Token.IsCancellationRequested)
{
throw new TimeoutException("Request timed out after 30 seconds");
}
}
// ストリーミングダウンロード
public async Task DownloadLargeFileAsync(string url, string filePath,
IProgress<long> progress = null, CancellationToken cancellationToken = default)
{
using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
var contentLength = response.Content.Headers.ContentLength;
using var stream = await response.Content.ReadAsStreamAsync();
using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
var buffer = new byte[8192];
long totalBytesRead = 0;
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken);
totalBytesRead += bytesRead;
progress?.Report(totalBytesRead);
}
}
// バッチ処理
public async Task<List<T>> ProcessBatchAsync<T>(IEnumerable<string> endpoints, int batchSize = 5)
{
var results = new List<T>();
var batches = endpoints.Chunk(batchSize);
foreach (var batch in batches)
{
var batchTasks = batch.Select(async endpoint =>
{
var response = await _httpClient.GetStringAsync(endpoint);
return JsonSerializer.Deserialize<T>(response);
});
var batchResults = await Task.WhenAll(batchTasks);
results.AddRange(batchResults);
// バッチ間の待機(レート制限対策)
await Task.Delay(100);
}
return results;
}
// HTTP/2対応の並行リクエスト
public async Task<List<ApiResponse>> GetConcurrentHttp2Async(List<string> endpoints)
{
// HTTP/2では同一接続で複数リクエストを並行処理可能
var tasks = endpoints.Select(async endpoint =>
{
try
{
var response = await _httpClient.GetAsync(endpoint);
return new ApiResponse
{
Endpoint = endpoint,
StatusCode = response.StatusCode,
Content = await response.Content.ReadAsStringAsync(),
Headers = response.Headers.ToDictionary(h => h.Key, h => h.Value.FirstOrDefault())
};
}
catch (Exception ex)
{
return new ApiResponse
{
Endpoint = endpoint,
Error = ex.Message
};
}
});
return (await Task.WhenAll(tasks)).ToList();
}
}
public class ApiResponse
{
public string Endpoint { get; set; }
public HttpStatusCode StatusCode { get; set; }
public string Content { get; set; }
public Dictionary<string, string> Headers { get; set; }
public string Error { get; set; }
}
// 拡張メソッド(.NET 6以降でChunkが利用可能、以前のバージョン用)
public static class EnumerableExtensions
{
public static IEnumerable<T[]> Chunk<T>(this IEnumerable<T> source, int size)
{
var chunk = new List<T>(size);
foreach (var item in source)
{
chunk.Add(item);
if (chunk.Count == size)
{
yield return chunk.ToArray();
chunk.Clear();
}
}
if (chunk.Count > 0)
yield return chunk.ToArray();
}
}