📢 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> | ||