IHttpClientFactory と .NET
この記事では、IHttpClientFactory
インターフェイスを使用して HttpClient
型を作成する方法と、依存関係の挿入 (DI)、ログ記録、構成などのさまざまな .NET の基礎について説明します。 HttpClient 型は、2012 年にリリースされた .NET Framework 4.5 で導入されました。 つまり、使われるようになってしばらく経ちます。 HttpClient
は、HTTP 要求を行い、Uri によって識別される Web リソースからの HTTP 応答を処理するために使用されます。 HTTP プロトコルによって、すべてのインターネット トラフィックの大部分が構成されます。
ベスト プラクティスを推進する最新のアプリケーション開発原則に従うことにより、IHttpClientFactory は、カスタム構成で HttpClient
インスタンスを作成できるファクトリ抽象化として機能します。 IHttpClientFactory は .NET Core 2.1 で導入されました。 一般的な HTTP ベースの .NET ワークロードでは、回復性のある、一時的な障害処理用のサードパーティ ミドルウェアを簡単に利用できます。
Note
アプリに Cookie が必要な場合は、アプリで IHttpClientFactory を使用しない方がよい場合があります。 クライアントを管理する別の方法については、「HTTP クライアントの使用に関するガイドライン」を参照してください。
重要
IHttpClientFactory
によって作成された HttpClient
インスタンスの有効期間の管理は、手動で作成されたインスタンスとは完全に異なります。 その方法は、IHttpClientFactory
によって作成された有効期間の短いクライアントを使うか、PooledConnectionLifetime
が設定された有効期間の長いクライアントを使うことです。 詳細については、「HttpClient の有効期間の管理」セクションと「HTTP クライアントの使用に関するガイドライン」を参照してください。
IHttpClientFactory
型
この記事で提供されているすべてのサンプル ソース コードで Microsoft.Extensions.Http
NuGet パッケージのインストールが必要です。 さらに、コード サンプルでは、無料の {JSON} Placeholder API からユーザー Todo
オブジェクトを取得するための HTTP GET
要求の使用方法を紹介しています。
AddHttpClient 拡張メソッドのいずれかを呼び出すと、IHttpClientFactory
と関連サービスが IServiceCollection に追加されます。 IHttpClientFactory
型には次のような利点があります。
HttpClient
クラスを DI 対応型として公開します。- 論理
HttpClient
インスタンスの名前付けと構成を一元化します。 HttpClient
でのハンドラーのデリゲートにより、送信ミドルウェアの概念が体系化されます。HttpClient
でのハンドラーのデリゲートを利用するために、Polly ベースのミドルウェアに対する拡張メソッドが提供されます。- 基になっている HttpClientHandler インスタンスのキャッシュと有効期間が管理されます。 自動管理により、
HttpClient
の有効期間を手動で管理するときの一般的なドメイン ネーム システム (DNS) の問題が発生しなくなります。 - ファクトリによって作成されたクライアントから送信されるすべての要求に対し、(ILogger によって) 構成可能なログ エクスペリエンスを追加します。
利用パターン
アプリで IHttpClientFactory
を使用するには複数の方法があります。
どの方法が最善かは、アプリの要件によって異なります。
基本的な使用方法
IHttpClientFactory
を登録するには、AddHttpClient
を呼び出します。
using Shared;
using BasicHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHttpClient();
builder.Services.AddTransient<TodoService>();
using IHost host = builder.Build();
サービスを利用する場合、DI を使用してコンストラクター パラメーターとして IHttpClientFactory
を要求できます。 次のコードでは、IHttpClientFactory
を使用して HttpClient
インスタンスを作成しています。
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;
namespace BasicHttp.Example;
public sealed class TodoService(
IHttpClientFactory httpClientFactory,
ILogger<TodoService> logger)
{
public async Task<Todo[]> GetUserTodosAsync(int userId)
{
// Create the client
using HttpClient client = httpClientFactory.CreateClient();
try
{
// Make HTTP GET request
// Parse JSON response deserialize into Todo types
Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
$"https://jsonplaceholder.typicode.com/todos?userId={userId}",
new JsonSerializerOptions(JsonSerializerDefaults.Web));
return todos ?? [];
}
catch (Exception ex)
{
logger.LogError("Error getting something fun to say: {Error}", ex);
}
return [];
}
}
前の例のように IHttpClientFactory
を使用するのは、既存のアプリをリファクタリングするのに適した方法です。 HttpClient
の使用方法に対する影響はありません。 既存のアプリで HttpClient
のインスタンスが作成されている場所を、CreateClient の呼び出しに置き換えます。
名前付きクライアント
名前付きクライアントは、次の場合に適しています。
- アプリで、多くの異なる
HttpClient
を使用する必要がある。 - 多くの
HttpClient
インスタンスには異なる構成があります。
名前付き HttpClient
の構成は、IServiceCollection
での登録時に指定できます:
using Shared;
using NamedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
string? httpClientName = builder.Configuration["TodoHttpClientName"];
ArgumentException.ThrowIfNullOrEmpty(httpClientName);
builder.Services.AddHttpClient(
httpClientName,
client =>
{
// Set the base address of the named client.
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
// Add a user-agent default request header.
client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
});
上記のコードでは、クライアントは次のもので構成されます。
"TodoHttpClientName"
の下の構成からプルされた名前。- ベース アドレス
https://jsonplaceholder.typicode.com/
。 "User-Agent"
ヘッダー。
構成を使用して HTTP クライアント名を指定することができます。これは、追加時や作成時にクライアントに誤った名前を付けないようにするのに役立ちます。 この例では、HTTP クライアント名を構成するために appsettings.js ファイルが使用されています。
{
"TodoHttpClientName": "JsonPlaceholderApi"
}
この構成を拡張して、HTTP クライアントがどのように機能するかについての詳細を格納するのは簡単です。 詳細については、「.NET での構成」を参照してください。
クライアントの作成
CreateClient が呼び出されるたびに、次のことが行われます。
HttpClient
の新しいインスタンスが作成されます。- 構成アクションが呼び出されます。
名前付きクライアントを作成するには、その名前を CreateClient
に渡します。
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Shared;
namespace NamedHttp.Example;
public sealed class TodoService
{
private readonly IHttpClientFactory _httpClientFactory = null!;
private readonly IConfiguration _configuration = null!;
private readonly ILogger<TodoService> _logger = null!;
public TodoService(
IHttpClientFactory httpClientFactory,
IConfiguration configuration,
ILogger<TodoService> logger) =>
(_httpClientFactory, _configuration, _logger) =
(httpClientFactory, configuration, logger);
public async Task<Todo[]> GetUserTodosAsync(int userId)
{
// Create the client
string? httpClientName = _configuration["TodoHttpClientName"];
using HttpClient client = _httpClientFactory.CreateClient(httpClientName ?? "");
try
{
// Make HTTP GET request
// Parse JSON response deserialize into Todo type
Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
$"todos?userId={userId}",
new JsonSerializerOptions(JsonSerializerDefaults.Web));
return todos ?? [];
}
catch (Exception ex)
{
_logger.LogError("Error getting something fun to say: {Error}", ex);
}
return [];
}
}
上記のコードでは、HTTP 要求でホスト名を指定する必要はありません。 クライアントに構成されているベース アドレスが使用されるため、コードではパスを渡すだけで済みます。
型指定されたクライアント
型指定されたクライアント:
- キーとして文字列を使用する必要なしに、名前付きクライアントと同じ機能を提供します。
- クライアントを使用するときに、IntelliSense とコンパイラのヘルプを提供します。
- 特定の
HttpClient
を構成してそれと対話する 1 つの場所を提供します。 たとえば、単一の型指定されたクライアントは、次のために使用される場合があります。- 単一のバックエンド エンドポイント用。
- エンドポイントを処理するすべてのロジックをカプセル化するため。
- DI に対応しており、アプリ内の必要な場所に挿入できます。
型指定されたクライアントは、そのコンストラクターで HttpClient
パラメーターを受け取ります。
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;
namespace TypedHttp.Example;
public sealed class TodoService(
HttpClient httpClient,
ILogger<TodoService> logger) : IDisposable
{
public async Task<Todo[]> GetUserTodosAsync(int userId)
{
try
{
// Make HTTP GET request
// Parse JSON response deserialize into Todo type
Todo[]? todos = await httpClient.GetFromJsonAsync<Todo[]>(
$"todos?userId={userId}",
new JsonSerializerOptions(JsonSerializerDefaults.Web));
return todos ?? [];
}
catch (Exception ex)
{
logger.LogError("Error getting something fun to say: {Error}", ex);
}
return [];
}
public void Dispose() => httpClient?.Dispose();
}
上のコードでは以下の操作が行われます。
- この構成は、型指定されたクライアントがサービス コレクションに追加されるときに設定されます。
HttpClient
は、クラススコープの変数 (フィールド) として割り当てられ、公開された API と共に使用されます。
HttpClient
の機能を公開する API 固有のメソッドを作成できます。 たとえば、GetUserTodosAsync
メソッドでは、ユーザー固有のTodo
オブジェクトを取得するためのコードがカプセル化されます。
次のコードでは、AddHttpClient を呼び出して、型指定されたクライアント クラスを登録しています。
using Shared;
using TypedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHttpClient<TodoService>(
client =>
{
// Set the base address of the typed client.
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
// Add a user-agent default request header.
client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
});
型指定されたクライアントは、DI で一時的として登録されます。 上記のコードで、AddHttpClient
は TodoService
を一時的なサービスとして登録します。 この登録では、ファクトリ メソッドを使用して次のことを行います。
HttpClient
のインスタンスを作成します。TodoService
のインスタンスを作成し、HttpClient
のインスタンスをそのコンストラクターに渡します。
重要
シングルトン サービスで型指定されたクライアントを使うと、危険な場合があります。 詳細については、シングルトン サービスでの型指定されたクライアントの回避に関するセクションを参照してください。
Note
型指定されたクライアントを AddHttpClient<TClient>
メソッドを使用して登録する場合、TClient
型には、パラメータとして HttpClient
を許可するコンストラクタが必要です。 さらに、TClient
型は DI コンテナを使用して個別に登録しないでください。登録すると、前登録が後の登録にとり上書きされるためです。
生成されたクライアント
IHttpClientFactory
は、Refit などのサードパーティ製ライブラリと組み合わせて使用できます。 Refit は、.NET 用の REST ライブラリです。 これにより、インターフェイス メソッドをエンドポイントにマッピングする宣言型の REST API 定義が可能になります。 インターフェイスの実装は RestService
によって動的に生成され、HttpClient
を使用して外部 HTTP の呼び出しを行います。
次の record
型があるとします:
namespace Shared;
public record class Todo(
int UserId,
int Id,
string Title,
bool Completed);
次の例は、Refit.HttpClientFactory
NuGet パッケージに依存しており、単純なインターフェイスです。
using Refit;
using Shared;
namespace GeneratedHttp.Example;
public interface ITodoService
{
[Get("/todos?userId={userId}")]
Task<Todo[]> GetUserTodosAsync(int userId);
}
上記の C# インターフェイスでは、次のことを行います。
Task<Todo[]>
インスタンスを返すGetUserTodosAsync
という名前のメソッドを定義します。- 外部 API に対するパスとクエリ文字列を使用して、
Refit.GetAttribute
属性を宣言します。
型指定されたクライアントを追加し、Refit を使用して実装を生成できます。
using GeneratedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Refit;
using Shared;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddRefitClient<ITodoService>()
.ConfigureHttpClient(client =>
{
// Set the base address of the named client.
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
// Add a user-agent default request header.
client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
});
定義されたインターフェイスは、DI および Refit によって提供される実装で、必要に応じて使用できます。
POST、PUT、DELETE の要求を行う
前の例では、すべての HTTP 要求で GET
HTTP 動詞が使用されています。 HttpClient
では、次のような他の HTTP 動詞もサポートされています。
POST
PUT
DELETE
PATCH
サポートされている HTTP 動詞の一覧については、「HttpMethod」を参照してください。 HTTP 要求の作成の詳細については、「HttpClient を使用して要求を送信する」 を参照してください。
次の例は、HTTP POST
要求の方法を示しています。
public async Task CreateItemAsync(Item item)
{
using StringContent json = new(
JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
Encoding.UTF8,
MediaTypeNames.Application.Json);
using HttpResponseMessage httpResponse =
await httpClient.PostAsync("/api/items", json);
httpResponse.EnsureSuccessStatusCode();
}
上記のコードの CreateItemAsync
メソッドは:
System.Text.Json
を使用してItem
パラメーターを JSON にシリアル化します。 これは、JsonSerializerOptions のインスタンスを使用して、シリアル化プロセスを構成します。- HTTP 要求の本文で送信するためにシリアル化された JSON をパッケージ化する StringContent のインスタンスを作成します。
- PostAsync を呼び出して、指定した URL に JSON の内容を送信します。 これは HttpClient.BaseAddress に追加される相対 URL です。
- 応答状態コードが成功を示していない場合に、EnsureSuccessStatusCode を呼び出して例外をスローします。
HttpClient
は、他の種類のコンテンツもサポートしています。 たとえば、MultipartContent とStreamContent です。 サポートされているコンテンツの一覧については、「HttpContent」を参照してください。
HTTP PUT
要求の例を次に示します。
public async Task UpdateItemAsync(Item item)
{
using StringContent json = new(
JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
Encoding.UTF8,
MediaTypeNames.Application.Json);
using HttpResponseMessage httpResponse =
await httpClient.PutAsync($"/api/items/{item.Id}", json);
httpResponse.EnsureSuccessStatusCode();
}
上記のコードは、POST
の例によく似ています。 UpdateItemAsync
メソッドは PostAsync
ではなく PutAsync を呼び出します。
HTTP DELETE
要求の例を次に示します。
public async Task DeleteItemAsync(Guid id)
{
using HttpResponseMessage httpResponse =
await httpClient.DeleteAsync($"/api/items/{id}");
httpResponse.EnsureSuccessStatusCode();
}
上記のコードの DeleteItemAsync
メソッドは DeleteAsync を呼び出します。 HTTP DELETE 要求には通常、本文が含まれていないため、DeleteAsync
メソッドは HttpContent
のインスタンスを受け入れるオーバーロードを提供しません。
HttpClient
でのさまざまな HTTP 動詞の使用の詳細については、「HttpClient」を参照してください。
HttpClient
有効期間管理
IHttpClientFactory
で CreateClient
を呼び出すたびに、HttpClient
の新しいインスタンスが返されます。 クライアント名ごとに 1 つの HttpClientHandler インスタンスが作成されます。 ファクトリによって HttpClientHandler
インスタンスの有効期間が管理されます。
IHttpClientFactory
は、リソースの消費量を減らすため、ファクトリによって作成された HttpClientHandler
のインスタンスをキャッシュします。 新しい HttpClient
インスタンスを作成するときに、キャッシュの HttpClientHandler
インスタンスの有効期間が切れていない場合はそれを再利用する場合があります。
通常、各ハンドラーでは基になる HTTP 接続プールが独自に管理されるため、ハンドラーはキャッシュすることが望まれます。 必要以上のハンドラーを作成すると、ソケット不足と接続の遅延が発生するおそれがあります。 また、一部のハンドラーは接続を無期限に開いており、DNS の変更にハンドラーが対応できないことがあります。
ハンドラーの既定の有効期間は 2 分です。 既定値をオーバーライドするには、次のように、IServiceCollection
で各クライアントに対して SetHandlerLifetime を呼び出します。
services.AddHttpClient("Named.Client")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
重要
IHttpClientFactory
によって作成される HttpClient
インスタンスは、短い有効期間であることが想定されます。
HttpMessageHandler
の有効期限が切れたときにそのリサイクルと再作成を行うことは、IHttpClientFactory
にとってハンドラーが DNS の変更に対応できるようにするために不可欠です。HttpClient
は作成時に特定のハンドラー インスタンスに関連付けられるため、新しいHttpClient
インスタンスを適切なタイミングで要求し、クライアントが更新されたハンドラーを確実に取得できるようにする必要があります。ファクトリによって作成されたこのような
HttpClient
インスタンスを破棄してもソケットは枯渇しません。破棄してもHttpMessageHandler
は破棄されないためです。IHttpClientFactory
はHttpClient
インスタンスの作成に使用されたリソース (具体的にはHttpMessageHandler
インスタンス) を追跡し、破棄します。それらの有効期間はすぐに期限切れになり、それらを使うHttpClient
がなくなるからです。
1 つの HttpClient
インスタンスを長期間存続させることは、IHttpClientFactory
の代替手段として使用できる一般的なパターンですが、このパターンには PooledConnectionLifetime
のような追加のセットアップが必要です。 PooledConnectionLifetime
を指定した有効期間の長いクライアントを使うか、IHttpClientFactory
によって作成された有効期間の短いクライアントを使うことができます。 アプリで使用する戦略の詳細については、「HTTP クライアントの使用に関するガイドライン」を参照してください。
HttpMessageHandler
を構成する
クライアントによって使用される内部 HttpMessageHandler の構成を制御することが必要な場合があります。
名前付きクライアントまたは型指定されたクライアントを追加すると、IHttpClientBuilder が返されます。 ConfigurePrimaryHttpMessageHandler 拡張メソッドを使用すると、IServiceCollection
でデリゲートを定義することができます。 デリゲートは、そのクライアントによって使用されるプライマリ HttpMessageHandler
の作成と構成に使用されます。
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
HttClientHandler
を構成すると、ハンドラーのさまざまな他のプロパティの中から HttpClient
インスタンスのプロキシを指定できます。 詳細については、クライアントごとのプロキシに関する記事を参照してください。
追加の設定
IHttpClientHandler
を制御するための追加の構成オプションがいくつかあります。
メソッド | 説明 |
---|---|
AddHttpMessageHandler | 名前付き HttpClient のメッセージ ハンドラーをさらに追加します。 |
AddTypedClient | TClient と、IHttpClientBuilder に関連付けられている名前付き HttpClient との間のバインディングを構成します。 |
ConfigureHttpClient | 名前付き HttpClient の構成に使用されるデリゲートを追加します。 |
ConfigurePrimaryHttpMessageHandler | 名前付き HttpClient に対して、依存関係挿入コンテナーからプライマリ HttpMessageHandler を構成します。 |
RedactLoggedHeaders | ログ記録する前に値を編集する必要がある HTTP ヘッダー名のコレクションを設定します。 |
SetHandlerLifetime | HttpMessageHandler を再利用できる時間を設定します。 名前付きの各クライアントには、独自の構成済みハンドラーの有効期間の値を設定できます。 |
UseSocketsHttpHandler | 名前付き HttpClient のプライマリ ハンドラとして使用されるように依存性の注入コンテナから新しいまたは前に追加した SocketsHttpHandler インスタンスを構成します。 (.NET 5+ のみ) |
SocketsHttpHandler と共に HttpClientFactory を使用する
HttpMessageHandler
の SocketsHttpHandler
実装は .NET Core 2.1 で追加されました。これにより PooledConnectionLifetime
を構成できます。 この設定は、ハンドラーが DNS の変更に対応できるようにするために使用されます。そのため、SocketsHttpHandler
の使用は IHttpClientFactory
の使用の代替手段と見なされます。 詳細については、「HttpClient の使用に関するガイドライン」を参照してください。
ただし、SocketsHttpHandler
と IHttpClientFactory
を一緒に使うと、構成可能性が向上します。 これらの API の両方を使うことで、低レベル (たとえば、動的な証明書の選択に LocalCertificateSelectionCallback
を使う) と高レベル (たとえば、DI 統合や複数のクライアント構成を活用する) の両方で構成可能性の利点を得ることができます。
両方の API を使うには:
- ConfigurePrimaryHttpMessageHandler または UseSocketsHttpHandler 経由で
PrimaryHandler
としてSocketsHttpHandler
を指定します (.NET 5+ のみ)。 - DNS を更新する間隔に基づいて SocketsHttpHandler.PooledConnectionLifetime を設定します。以前
HandlerLifetime
にあった値などが例として挙げられます。 - (オプション)
SocketsHttpHandler
が接続ポールとリサイクルを処理するため、IHttpClientFactory
レベルのハンドラ リサイクルは不要になりました。HandlerLifetime
をTimeout.InfiniteTimeSpan
に設定して、サイズ変更を無効にすることもできます。
services.AddHttpClient(name)
.UseSocketsHttpHandler((handler, _) =>
handler.PooledConnectionLifetime = TimeSpan.FromMinutes(2)) // Recreate connection every 2 minutes
.SetHandlerLifetime(Timeout.InfiniteTimeSpan); // Disable rotation, as it is handled by PooledConnectionLifetime
上の例では、既定の HandlerLifetime
値に合わせて、図解の目的で 2 分を任意に選択しました。 DNS またはその他のネットワーク変更の予想される頻度に基づいて値を選択する必要があります。 詳細については、HttpClient
ガイドラインの「DNS の動作」セクションと PooledConnectionLifetime API ドキュメントの「備考」セクションを参照してください。
シングルトン サービスで型指定されたクライアントを使う
"名前付きクライアント" の方法を使用する場合、IHttpClientFactory
はサービスに挿入され、HttpClient
インスタンスは HttpClient
が必要になるたびに CreateClient を呼び出すことによって作成されます。
ただし、"型指定されたクライアント" の方法では、型指定されたクライアントは、通常はサービスに挿入される一時的なオブジェクトになります。 これにより問題が発生するおそれがあります。型指定されたクライアントをシングルトン サービスに挿入できるためです。
重要
型指定されたクライアントは、IHttpClientFactory
によって作成された HttpClient
インスタンスと同じ意味で、短い有効期間であることが想定されます (詳細については、「HttpClient
の有効期間の管理」を参照)。 型指定されたクライアント インスタンスが作成されるとすぐに、IHttpClientFactory
ではそれを制御できなくなります。 型指定されたクライアント インスタンスがシングルトンで取得された場合、DNS の変更に対応できなくなり、IHttpClientFactory
の目的の 1 つが失われるおそれがあります。
シングルトン サービスで HttpClient
インスタンスを使う必要がある場合は、次のオプションを検討してください。
- 代わりに "名前付きクライアント" の方法を使い、シングルトン サービスに
IHttpClientFactory
を挿入し、必要に応じてHttpClient
インスタンスを再作成します。 - "型指定されたクライアント" の方法が必要な場合は、構成済みの
PooledConnectionLifetime
と共にプライマリ ハンドラーとしてSocketsHttpHandler
を使います。SocketsHttpHandler
とIHttpClientFactory
を使う方法について詳しくは、セクション「SocketsHttpHandler と共に IHttpClientFactory を使用する」 を参照してください。
IHttpClientFactory のメッセージ ハンドラーのスコープ
IHttpClientFactory
は、各 HttpMessageHandler
インスタンスごとに個別の DI スコープを作成します。 これらの DI スコープは、アプリケーション DI スコープ (たとえば、ASP.NET 受信要求スコープやユーザーが作成した手動 DI スコープなど) とは別であるため、スコープ付きサービス インスタンスは共有されません。 メッセージ ハンドラーのスコープはハンドラーの有効期間に関連付けられ、アプリケーション スコープを上回る可能性があります。これにより、たとえば、複数の受信要求間で、同じ挿入されたスコープ付き依存関係を持つ同じ HttpMessageHandler
インスタンスが再利用される可能性があります。
HttpMessageHandler
インスタンス内で (HttpContext
のデータなどの) スコープ関連の情報をキャッシュしないことと、機密情報を漏洩しないように注意してスコープ付き依存関係を使うことを強くお勧めします。
メッセージ ハンドラーからアプリ DI スコープにアクセスする必要がある場合は、認証の例として、スコープ対応ロジックを別の一時的な DelegatingHandler
にカプセル化し、IHttpClientFactory
キャッシュから HttpMessageHandler
インスタンスの周囲にラップします。 登録済みの 名前付きクライアント のハンドラー呼び出し IHttpMessageHandlerFactory.CreateHandler にアクセスします。 その場合は、構築されたハンドラーを使用して自分で HttpClient
インスタンスを作成します。
次の例は、スコープ対応の DelegatingHandler
を使用して HttpClient
を作成する方法を示しています:
if (scopeAwareHandlerType != null)
{
if (!typeof(DelegatingHandler).IsAssignableFrom(scopeAwareHandlerType))
{
throw new ArgumentException($"""
Scope aware HttpHandler {scopeAwareHandlerType.Name} should
be assignable to DelegatingHandler
""");
}
// Create top-most delegating handler with scoped dependencies
scopeAwareHandler = (DelegatingHandler)_scopeServiceProvider.GetRequiredService(scopeAwareHandlerType); // should be transient
if (scopeAwareHandler.InnerHandler != null)
{
throw new ArgumentException($"""
Inner handler of a delegating handler {scopeAwareHandlerType.Name} should be null.
Scope aware HttpHandler should be registered as Transient.
""");
}
}
// Get or create HttpMessageHandler from HttpClientFactory
HttpMessageHandler handler = _httpMessageHandlerFactory.CreateHandler(name);
if (scopeAwareHandler != null)
{
scopeAwareHandler.InnerHandler = handler;
handler = scopeAwareHandler;
}
HttpClient client = new(handler);
さらに回避策として、スコープ対応 DelegatingHandler
を登録し、現在のアプリ スコープにアクセスできる一時的なサービスによる既定 IHttpClientFactory
の登録をオーバーライドするための拡張メソッドを使用できます:
public static IHttpClientBuilder AddScopeAwareHttpHandler<THandler>(
this IHttpClientBuilder builder) where THandler : DelegatingHandler
{
builder.Services.TryAddTransient<THandler>();
if (!builder.Services.Any(sd => sd.ImplementationType == typeof(ScopeAwareHttpClientFactory)))
{
// Override default IHttpClientFactory registration
builder.Services.AddTransient<IHttpClientFactory, ScopeAwareHttpClientFactory>();
}
builder.Services.Configure<ScopeAwareHttpClientFactoryOptions>(
builder.Name, options => options.HttpHandlerType = typeof(THandler));
return builder;
}
詳細については、完全な例を参照してください。
関連項目
.NET