「C Sharpとネットワーク - HttpClient」の版間の差分

📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

 
(同じ利用者による、間の2版が非表示)
101行目: 101行目:


== ソリューション ==
== ソリューション ==
==== 手順 ====
==== 方法 1 : static / readonly ====
<code>HttpClient</code>クラスは、<code>private</code>キーワードおよび<code>static</code>キーワードを指定したプロパティとして持つ必要がある。<br>
<code>HttpClient</code>クラスは、<code>private</code>キーワードおよび<code>static</code>キーワードを指定したプロパティとして持つ必要がある。<br>
<br>
<br>
107行目: 107行目:
HttpClientを使用した実装をする時は、インスタンスを静的変数(static)にして使用するとの記載がある。<br>
HttpClientを使用した実装をする時は、インスタンスを静的変数(static)にして使用するとの記載がある。<br>
<br>
<br>
==== サンプルコード ====
まず、<code>HttpClient</code>クラスのオブジェクトを生成する。<br>
まず、<code>HttpClient</code>クラスのオブジェクトを生成する。<br>
この時、タイムアウトの設定等はコンストラクタで行う必要がある。<br>
この時、タイムアウトの設定等はコンストラクタで行う必要がある。<br>
145行目: 144行目:
また、1つの<code>HttpClient</code>クラスは1つのソケット(1つのホスト)として使用した方がよいため、<br>
また、1つの<code>HttpClient</code>クラスは1つのソケット(1つのホスト)として使用した方がよいため、<br>
異なるホストにもリクエストを投げる場合は、別の<code>HttpClient</code>クラスのオブジェクトを生成する方がよい。<br>
異なるホストにもリクエストを投げる場合は、別の<code>HttpClient</code>クラスのオブジェクトを生成する方がよい。<br>
<br>
==== 方法 2 : HttpClientFactory (単一のベースURI) ====
* 依存性注入 (DI) の設定
*: Host.CreateDefaultBuilderメソッドを使用して、.NETの標準的なDIコンテナを設定する。
*: サービスの登録はConfigureServicesメソッドで行う。
*: 異なるライフタイムスコープ (Transient, Scoped, Singleton) から適切なものを選択する。
<br>
HttpClientFactoryでは、2つのパターンがある。<br>
* 名前付きHttpClient
*: services.AddHttpClient("github", ...) で登録する。
*: IHttpClientFactory.CreateClient("github")で取得する。
*: 同じ名前で登録されたクライアントは同じ設定を共有する。
*: <br>
* 型付きHttpClient
*: services.AddHttpClient<GitHubService>メソッドで登録する。
*: コンストラクタインジェクションで自動的に注入する。
*: サービスごとに特化した実装が可能になる。
<br>
Nugetを使用して、以下に示すライブラリをインストールする。<br>
* Microsoft.Extensions.Http
* Microsoft.Extensions.Hosting
<br>
<syntaxhighlight lang="c#">
using System;
using System.Threading.Tasks;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
class Program
{
    static async Task Main(string[] args)
    {
        // GenericHostを使用してアプリケーションを構築
        // これにより、依存性注入、構成、ログ等の機能が利用可能になる
        var host = Host.CreateDefaultBuilder(args).ConfigureServices((context, services) => {
          // 方法 1 : 名前付きHttpClientを使用する場合
          // 名前付きHttpClientの登録
          // "github"という名前で、GitHubのAPIにアクセスするためのHttpClientを設定
          services.AddHttpClient("github", client => {
              // ベースとなるURIを設定
              client.BaseAddress = new Uri("https://api.github.com/");
              // User-Agentヘッダを設定 (GitHubのAPIでは必須)
              client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
          });
          // 方法 2 : 型付きHttpClientを使用する場合
          // 型付きHttpClientの登録
          // GitHubServiceクラスに特化したHttpClientを自動的に注入
          services.AddHttpClient<GitHubService>();
          // ExampleServiceをDIコンテナに登録
          // TransientスコープでサービスをDIコンテナに登録(毎回新しいインスタンスが作成される)
          services.AddTransient<IExampleService, ExampleService>();
        }).Build();
        // DIコンテナからサービスを取得し、実行
        var service = host.Services.GetRequiredService<IExampleService>();
        await service.RunExample();
    }
}
/// <summary>
/// 型付きHttpClientを使用するサービスクラス
/// GitHubのAPIに特化した操作を提供
/// </summary>
public class GitHubService
{
    private readonly HttpClient _httpClient;
    // コンストラクタインジェクション
    // DIコンテナにより、設定済みのHttpClientが自動的に注入される
    public GitHubService(HttpClient client)
    {
      _httpClient = client;
      // このHttpClientインスタンスに対する固有の設定
      _httpClient.BaseAddress = new Uri("https://api.github.com/");
      _httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
    }
    /// <summary>
    /// GitHubのAPIからデータを取得する
    /// </summary>
    /// <returns>API応答の文字列</returns>
    public async Task<string> GetApiResponse()
    {
      // dotnet/runtimeリポジトリの情報を取得
      return await _httpClient.GetStringAsync("repos/dotnet/runtime");
    }
}
/// <summary>
/// サービスのインターフェース定義
/// 依存性の注入とテストを容易にするために使用
/// </summary>
public interface IExampleService
{
    Task RunExample();
}
/// <summary>
/// HttpClientFactoryの使用例を示すサービスクラス
/// 名前付きHttpClientと型付きHttpClientの両方の使用例を提供
/// </summary>
public class ExampleService : IExampleService
{
    private readonly IHttpClientFactory _clientFactory;
    private readonly GitHubService _githubService;
    /// <summary>
    /// コンストラクタで依存関係を注入
    /// </summary>
    /// <param name="clientFactory">HttpClientFactory - 名前付きクライアントの作成に使用</param>
    /// <param name="githubService">GitHubService - 型付きHttpClientの例として使用</param>
    public ExampleService(IHttpClientFactory clientFactory, GitHubService githubService)
    {
      _clientFactory = clientFactory;
      _githubService = githubService;
    }
    /// <summary>
    /// HttpClientFactoryの両方の使用パターンを実演
    /// </summary>
    public async Task RunExample()
    {
      // 方法 1 : 名前付きHttpClientの使用例
      try
      {
          // "github"という名前で設定されたHttpClientを取得
          var client = _clientFactory.CreateClient("github");
          var response = await client.GetStringAsync("repos/dotnet/runtime");
          Console.WriteLine("Named HttpClient Response:");
          Console.WriteLine(response.Substring(0, 200) + "...");
      }
      catch (Exception ex)
      {
          Console.WriteLine($"Named client error: {ex.Message}");
      }
      // // 方法 2 : 型付きHttpClientの使用例
      try
      {
          // 注入されたGitHubServiceを使用
          var response = await _githubService.GetApiResponse();
          Console.WriteLine("Typed HttpClient Response:");
          Console.WriteLine(response.Substring(0, 200) + "...");
      }
      catch (Exception ex)
      {
          Console.WriteLine($"Typed client error: {ex.Message}");
      }
    }
}
</syntaxhighlight>
<br>
==== 方法 3 : HttpClientFactory (複数のベースURI) ====
* 名前付きHttpClientを使用する方法
** メリット
**: 個別の設定が容易
** デメリット
**: 文字列ベースの名前指定
**: 型安全性が低い
*: <br>
* 型付きHttpClientを使用する方法
** メリット
**: 型安全性が高い
**: APIごとに特化した実装が可能
**: テストが容易
** デメリット
**: クラス数が増加
**: 各APIに対して個別の実装が必要
*: <br>
* 動的にベースURIを切り替える方法
** メリット
**: 柔軟性が高い
**: 設定ファイルでの管理が容易
**: 実行時の切り替えが可能
** デメリット
**: 複雑な実装
<br>
<syntaxhighlight lang="json">
# 動的なベースURI切り替えで使用
# appsettings.json
{
  "ApiSettings": {
    "BaseUrls": {
      "github": "https://api.github.com/",
      "weather": "https://api.weather.com/",
      "other": "https://api.other.com/"
    }
  }
}
</syntaxhighlight>
<br>
<syntaxhighlight lang="c#">
using System;
using System.Threading.Tasks;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
class Program
{
    static async Task Main(string[] args)
    {
      var host = Host.CreateDefaultBuilder(args).ConfigureServices((context, services) => {
          // 方法 1 : 異なる名前で複数のHttpClientを登録
          services.AddHttpClient("github", client => {
            client.BaseAddress = new Uri("https://api.github.com/");
            client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
          });
          services.AddHttpClient("weather", client => {
            client.BaseAddress = new Uri("https://api.weather.com/");
            client.DefaultRequestHeaders.Add("User-Agent", "Weather-Service");
          });
          // 方法 2 : 型付きHttpClientを各APIサービス用に登録
          services.AddHttpClient<GitHubService>();
          services.AddHttpClient<WeatherService>();
          // 方法 3 : 設定情報を含むサービスを登録
          services.Configure<ApiSettings>(context.Configuration.GetSection("ApiSettings"));
          services.AddHttpClient<MultiBaseUriService>();
          services.AddTransient<IMultiApiService, MultiApiService>();
      }).Build();
      var service = host.Services.GetRequiredService<IMultiApiService>();
      await service.RunExample();
    }
}
// APIの設定を保持するクラス
public class ApiSettings
{
    public Dictionary<string, string> BaseUrls { get; set; } = new();
}
// 複数のベースURIを扱うサービス
public class MultiBaseUriService
{
    private readonly HttpClient _httpClient;
    private readonly ApiSettings _settings;
    private readonly Dictionary<string, string> _baseUrls;
    public MultiBaseUriService(HttpClient client, IOptions<ApiSettings> settings)
    {
      _httpClient = client;
      _settings = settings.Value;
      _baseUrls = _settings.BaseUrls;
    }
    public async Task<string> SendRequest(string apiKey, string endpoint)
    {
      if (!_baseUrls.TryGetValue(apiKey, out var baseUrl))
      {
          throw new ArgumentException($"Unknown API key: {apiKey}");
      }
      var fullUrl = new Uri(new Uri(baseUrl), endpoint);
      return await _httpClient.GetStringAsync(fullUrl);
    }
}
// GithubのAPI用サービス
public class GitHubService
{
    private readonly HttpClient _httpClient;
    public GitHubService(HttpClient client)
    {
      _httpClient = client;
      _httpClient.BaseAddress = new Uri("https://api.github.com/");
      _httpClient.DefaultRequestHeaders.Add("User-Agent", "GitHub-Service");
    }
    public async Task<string> GetRepositoryInfo(string repo)
    {
      return await _httpClient.GetStringAsync($"repos/{repo}");
    }
}
// 気象API用サービス
public class WeatherService
{
    private readonly HttpClient _httpClient;
    public WeatherService(HttpClient client)
    {
      _httpClient = client;
      _httpClient.BaseAddress = new Uri("https://api.weather.com/");
      _httpClient.DefaultRequestHeaders.Add("User-Agent", "Weather-Service");
    }
    public async Task<string> GetWeatherInfo(string location)
    {
      return await _httpClient.GetStringAsync($"weather/{location}");
    }
}
// 複数APIを利用するサービスのインターフェース
public interface IMultiApiService
{
    Task RunExample();
}
// 複数のAPIを利用する実装
public class MultiApiService : IMultiApiService
{
    private readonly IHttpClientFactory  _clientFactory;
    private readonly GitHubService      _githubService;
    private readonly WeatherService      _weatherService;
    private readonly MultiBaseUriService _multiBaseUriService;
    public MultiApiService(IHttpClientFactory clientFactory, GitHubService githubService, WeatherService weatherService,
                          MultiBaseUriService multiBaseUriService)
    {
      _clientFactory = clientFactory;
      _githubService = githubService;
      _weatherService = weatherService;
      _multiBaseUriService = multiBaseUriService;
    }
    public async Task RunExample()
    {
      // 方法 1 : 名前付きHttpClientの使用
      try
      {
          var githubClient = _clientFactory.CreateClient("github");
          var weatherClient = _clientFactory.CreateClient("weather");
          var githubResponse = await githubClient.GetStringAsync("repos/dotnet/runtime");
          var weatherResponse = await weatherClient.GetStringAsync("weather/tokyo");
          Console.WriteLine("Named Clients Response:");
          Console.WriteLine($"GitHub: {githubResponse.Substring(0, 100)}...");
          Console.WriteLine($"Weather: {weatherResponse.Substring(0, 100)}...");
      }
      catch (Exception ex)
      {
          Console.WriteLine($"Named clients error: {ex.Message}");
      }
      // 方法 2 : 型付きHttpClientの使用
      try
      {
          var githubInfo = await _githubService.GetRepositoryInfo("dotnet/runtime");
          var weatherInfo = await _weatherService.GetWeatherInfo("tokyo");
          Console.WriteLine("\nTyped Clients Response:");
          Console.WriteLine($"GitHub: {githubInfo.Substring(0, 100)}...");
          Console.WriteLine($"Weather: {weatherInfo.Substring(0, 100)}...");
      }
      catch (Exception ex)
      {
          Console.WriteLine($"Typed clients error: {ex.Message}");
      }
      // 方法 3 : 動的なベースURI切り替え
      try
      {
          var githubResponse = await _multiBaseUriService.SendRequest("github", "repos/dotnet/runtime");
          var weatherResponse = await _multiBaseUriService.SendRequest("weather", "weather/tokyo");
          Console.WriteLine("\nMulti Base URI Service Response:");
          Console.WriteLine($"GitHub: {githubResponse.Substring(0, 100)}...");
          Console.WriteLine($"Weather: {weatherResponse.Substring(0, 100)}...");
      }
      catch (Exception ex)
      {
          Console.WriteLine($"Multi base URI error: {ex.Message}");
      }
    }
}
</syntaxhighlight>
<br><br>
<br><br>