ASP.NET Core で IHttpClientFactory を使用して HTTP 要求を行う

作成者: Kirk LarkinSteve GordonGlenn CondronRyan Nowak

アプリ内で HttpClient インスタンスを構成して作成するために、IHttpClientFactory を登録して使用できます。 IHttpClientFactory には次のような利点があります。

  • 論理 HttpClient インスタンスの名前付けと構成を一元化します。 たとえば、GitHub にアクセスするために、github という名前のクライアントを登録して構成できます。 一般的なアクセスのために、既定のクライアントを登録できます。
  • HttpClient でのハンドラーのデリゲートにより、送信ミドルウェアの概念が体系化されます。 HttpClient でのハンドラーのデリゲートを利用するために、Polly ベースのミドルウェアに対する拡張機能が提供されます。
  • 基になっている HttpClientMessageHandler インスタンスのプールと有効期間が管理されます。 自動管理により、HttpClient の有効期間を手動で管理するときの一般的な DNS (ドメイン ネーム システム) の問題が発生しなくなります。
  • ファクトリによって作成されたクライアントから送信されるすべての要求に対し、(ILogger によって) 構成可能なログ エクスペリエンスを追加します。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

このトピックのバージョンのサンプル コードでは、HTTP 応答で返された JSON コンテンツを、System.Text.Json を使用して逆シリアル化します。 Json.NET および ReadAsAsync<T> を使用するサンプルについては、バージョン セレクターを使用して、このトピックの 2.x バージョンを選択してください。

利用パターン

アプリで IHttpClientFactory を使用するには複数の方法があります。

どの方法が最善かは、アプリの要件によって異なります。

基本的な使用方法

IHttpClientFactory は、AddHttpClient を呼び出すことによって登録できます。

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpClient();
        // Remaining code deleted for brevity.

IHttpClientFactory は、依存関係の挿入 (DI) を使用して要求できます。 次のコードでは、IHttpClientFactory を使用して HttpClient インスタンスを作成しています。

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubBranch> Branches { get; private set; }

    public bool GetBranchesError { get; private set; }

    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            Branches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(responseStream);
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }
    }
}

前の例のように IHttpClientFactory を使用するのは、既存のアプリをリファクタリングするのに適した方法です。 HttpClient の使用方法に対する影響はありません。 既存のアプリで HttpClient のインスタンスが作成されている場所を、CreateClient の呼び出しに置き換えます。

名前付きクライアント

名前付きクライアントは、次の場合に適しています。

  • アプリで、多くの異なる HttpClient を使用する必要がある。
  • 多くの HttpClient の構成が異なる。

名前付き HttpClient の構成は、Startup.ConfigureServices での登録時に指定できます。

services.AddHttpClient("github", c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    // Github API versioning
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    // Github requires a user-agent
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

上記のコードでは、クライアントに次のものが構成されます。

  • ベース アドレス https://api.github.com/
  • GitHub API を使用するために必要な 2 つのヘッダー。

CreateClient

CreateClient が呼び出されるたびに、次のことが行われます。

  • HttpClient の新しいインスタンスが作成されます。
  • 構成アクションが呼び出されます。

名前付きクライアントを作成するには、その名前を CreateClient に渡します。

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }

    public bool GetPullRequestsError { get; private set; }

    public bool HasPullRequests => PullRequests.Any();

    public NamedClientModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "repos/dotnet/AspNetCore.Docs/pulls");

        var client = _clientFactory.CreateClient("github");

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            PullRequests = await JsonSerializer.DeserializeAsync
                    <IEnumerable<GitHubPullRequest>>(responseStream);
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}

上記のコードでは、要求でホスト名を指定する必要はありません。 クライアントに構成されているベース アドレスが使用されるため、コードではパスを渡すだけで済みます。

型指定されたクライアント

型指定されたクライアント:

  • キーとして文字列を使用する必要なしに、名前付きクライアントと同じ機能を提供します。
  • クライアントを使用するときに、IntelliSense とコンパイラのヘルプが提供されます。
  • 特定の HttpClient を構成してそれと対話する 1 つの場所を提供します。 たとえば、単一の型指定されたクライアントは、次のために使用される場合があります。
    • 単一のバックエンド エンドポイント用。
    • エンドポイントを処理するすべてのロジックをカプセル化するため。
  • DI に対応しており、アプリ内の必要な場所に挿入できます。

型指定されたクライアントは、そのコンストラクターで HttpClient パラメーターを受け取ります。

public class GitHubService
{
    public HttpClient Client { get; }

    public GitHubService(HttpClient client)
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        // GitHub API versioning
        client.DefaultRequestHeaders.Add("Accept",
            "application/vnd.github.v3+json");
        // GitHub requires a user-agent
        client.DefaultRequestHeaders.Add("User-Agent",
            "HttpClientFactory-Sample");

        Client = client;
    }

    public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
    {
        return await Client.GetFromJsonAsync<IEnumerable<GitHubIssue>>(
          "/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
    }
}

上のコードでは以下の操作が行われます。

  • 構成が型指定されたクライアントに移動されます。
  • HttpClient オブジェクトは、パブリック プロパティとして公開されます。

HttpClient の機能を公開する API 固有のメソッドを作成できます。 たとえば、GetAspNetDocsIssues メソッドでは、未解決の問題を取得するためのコードがカプセル化されています。

次のコードでは、Startup.ConfigureServices 内で AddHttpClient を呼び出して、型指定されたクライアント クラスを登録しています。

services.AddHttpClient<GitHubService>();

型指定されたクライアントは、DI で一時的として登録されます。 上記のコードで、AddHttpClientGitHubService を一時的なサービスとして登録します。 この登録では、ファクトリ メソッドを使用して次のことを行います。

  1. HttpClient のインスタンスを作成します。
  2. GitHubService のインスタンスを作成し、HttpClient のインスタンスをそのコンストラクターに渡します。

型指定されたクライアントは、直接挿入して使用できます。

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;

    public IEnumerable<GitHubIssue> LatestIssues { get; private set; }

    public bool HasIssue => LatestIssues.Any();

    public bool GetIssuesError { get; private set; }

    public TypedClientModel(GitHubService gitHubService)
    {
        _gitHubService = gitHubService;
    }

    public async Task OnGet()
    {
        try
        {
            LatestIssues = await _gitHubService.GetAspNetDocsIssues();
        }
        catch(HttpRequestException)
        {
            GetIssuesError = true;
            LatestIssues = Array.Empty<GitHubIssue>();
        }
    }
}

型指定されたクライアントのコンストラクターではなく、Startup.ConfigureServices での登録時に型指定されたクライアントの構成を指定できます。

services.AddHttpClient<RepoService>(c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

HttpClient は、型指定されたクライアント内にカプセル化できます。 プロパティとして公開するのではなく、HttpClient インスタンスを内部的に呼び出すパブリック メソッドを定義します。

public class RepoService
{
    // _httpClient isn't exposed publicly
    private readonly HttpClient _httpClient;

    public RepoService(HttpClient client)
    {
        _httpClient = client;
    }

    public async Task<IEnumerable<string>> GetRepos()
    {
        var response = await _httpClient.GetAsync("aspnet/repos");

        response.EnsureSuccessStatusCode();

        using var responseStream = await response.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync
            <IEnumerable<string>>(responseStream);
    }
}

上記のコードでは、HttpClient はプライベート フィールドに格納されます。 HttpClient へのアクセスは、パブリック GetRepos メソッドによって行われます。

生成されたクライアント

IHttpClientFactory は、Refit などのサードパーティ製ライブラリと組み合わせて使用できます。 Refit は、.NET 用の REST ライブラリです。 REST API をライブ インターフェイスに変換します。 インターフェイスの実装は RestService によって動的に生成され、HttpClient を使用して外部 HTTP の呼び出しを行います。

インターフェイスと応答は、外部の API とその応答を表すように定義されます。

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

型指定されたクライアントを追加し、Refit を使用して実装を生成できます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("http://localhost:5000");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddControllers();
}

定義されたインターフェイスは、DI および Refit によって提供される実装で必要に応じて使用できます。

[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IHelloClient _client;

    public ValuesController(IHelloClient client)
    {
        _client = client;
    }

    [HttpGet("/")]
    public async Task<ActionResult<Reply>> Index()
    {
        return await _client.GetMessageAsync();
    }
}

POST、PUT、DELETE の要求を行う

前の例では、すべての HTTP 要求で GET HTTP 動詞が使用されています。 HttpClient では、次のような他の HTTP 動詞もサポートされています。

  • POST
  • PUT
  • Del
  • PATCH

サポートされている HTTP 動詞の一覧については、「HttpMethod」を参照してください。

次の例は、HTTP POST 要求の方法を示しています。

public async Task CreateItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
        Encoding.UTF8,
        "application/json");

    using var httpResponse =
        await _httpClient.PostAsync("/api/TodoItems", todoItemJson);

    httpResponse.EnsureSuccessStatusCode();
}

上記のコードの CreateItemAsync メソッドは:

  • System.Text.Json を使用して TodoItem パラメーターを JSON にシリアル化します。 これは、JsonSerializerOptions のインスタンスを使用して、シリアル化プロセスを構成します。
  • HTTP 要求の本文で送信するためにシリアル化された JSON をパッケージ化する StringContent のインスタンスを作成します。
  • PostAsync を呼び出して、指定した URL に JSON の内容を送信します。 これは HttpClient.BaseAddress に追加される相対 URL です。
  • 応答状態コードが成功を示していない場合に、EnsureSuccessStatusCode を呼び出して例外をスローします。

HttpClient は、他の種類のコンテンツもサポートしています。 たとえば、MultipartContentStreamContent です。 サポートされているコンテンツの一覧については、「HttpContent」を参照してください。

HTTP PUT 要求の例を次に示します。

public async Task SaveItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem),
        Encoding.UTF8,
        "application/json");

    using var httpResponse =
        await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);

    httpResponse.EnsureSuccessStatusCode();
}

上記のコードは、POST の例によく似ています。 SaveItemAsync メソッドは PostAsync ではなく PutAsync を呼び出します。

HTTP DELETE 要求の例を次に示します。

public async Task DeleteItemAsync(long itemId)
{
    using var httpResponse =
        await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");

    httpResponse.EnsureSuccessStatusCode();
}

上記のコードの DeleteItemAsync メソッドは DeleteAsync を呼び出します。 HTTP DELETE 要求には通常、本文が含まれていないため、DeleteAsync メソッドは HttpContent のインスタンスを受け入れるオーバーロードを提供しません。

HttpClient でのさまざまな HTTP 動詞の使用の詳細については、「HttpClient」を参照してください。

送信要求ミドルウェア

HttpClient は、送信 HTTP 要求用にリンクできるハンドラーのデリゲートの概念を備えています。 IHttpClientFactory:

  • 各名前付きクライアントに適用するハンドラーの定義が簡単になります。

  • 送信要求ミドルウェア パイプラインを構築するための複数のハンドラーの登録とチェーン化がサポートされています。 これらの各ハンドラーでは、送信要求の前と後に処理を実行できます。 このパターンは次のようなものです。

    • ASP.NET Core での受信ミドルウェア パイプラインに似ています。
    • 次のような HTTP 要求に関する横断的な問題を管理するためのメカニズムが提供されます。
      • キャッシュ
      • エラー処理
      • シリアル化
      • ログ

デリゲート ハンドラーを作成するには、次のようにします。

  • DelegatingHandler から派生します。
  • SendAsync をオーバーライドします。 パイプライン内の次のハンドラーに要求を渡す前に、コードを実行します。
public class ValidateHeaderHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (!request.Headers.Contains("X-API-KEY"))
        {
            return new HttpResponseMessage(HttpStatusCode.BadRequest)
            {
                Content = new StringContent(
                    "You must supply an API key header called X-API-KEY")
            };
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

上記のコードでは、X-API-KEY ヘッダーが要求に含まれているかどうかが確認されます。 X-API-KEY がない場合は、BadRequest が返されます。

Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler を使用して HttpClient の構成に複数のハンドラーを追加できます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<ValidateHeaderHandler>();

    services.AddHttpClient("externalservice", c =>
    {
        // Assume this is an "external" service which requires an API KEY
        c.BaseAddress = new Uri("https://localhost:5001/");
    })
    .AddHttpMessageHandler<ValidateHeaderHandler>();

    // Remaining code deleted for brevity.

上記のコードでは、ValidateHeaderHandler が DI で登録されます。 登録が済むと、AddHttpMessageHandler を呼び出してハンドラーの型を渡すことができます。

実行する順序で、複数のハンドラーを登録することができます。 最後の HttpClientHandler が要求を実行するまで、各ハンドラーは次のハンドラーをラップします。

services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();

services.AddHttpClient("clientwithhandlers")
    // This handler is on the outside and called first during the 
    // request, last during the response.
    .AddHttpMessageHandler<SecureRequestHandler>()
    // This handler is on the inside, closest to the request being 
    // sent.
    .AddHttpMessageHandler<RequestDataHandler>();

送信要求ミドルウェアで DI を使用する

IHttpClientFactory によって新しいデリゲート ハンドラーが作成される際、ハンドラーのコンストラクター パラメーターを満たすために DI が使用されます。 IHttpClientFactory により、ハンドラーごとに 個別の DI スコープが作成されます。これにより、ハンドラーによって "スコープ付き" サービスが使用されるときに、予期しない動作が発生する可能性があります。

たとえば、識別子 OperationId を持つ操作としてタスクを表す、次のインターフェイスとその実装について考えてみます。

public interface IOperationScoped 
{
    string OperationId { get; }
}

public class OperationScoped : IOperationScoped
{
    public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

その名前が示すように、IOperationScoped は "スコープ付き" 有効期間を使用して DI に登録されます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<TodoContext>(options =>
        options.UseInMemoryDatabase("TodoItems"));

    services.AddHttpContextAccessor();

    services.AddHttpClient<TodoClient>((sp, httpClient) =>
    {
        var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;

        // For sample purposes, assume TodoClient is used in the context of an incoming request.
        httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
                                         httpRequest.Host, httpRequest.PathBase));
        httpClient.Timeout = TimeSpan.FromSeconds(5);
    });

    services.AddScoped<IOperationScoped, OperationScoped>();
    
    services.AddTransient<OperationHandler>();
    services.AddTransient<OperationResponseHandler>();

    services.AddHttpClient("Operation")
        .AddHttpMessageHandler<OperationHandler>()
        .AddHttpMessageHandler<OperationResponseHandler>()
        .SetHandlerLifetime(TimeSpan.FromSeconds(5));

    services.AddControllers();
    services.AddRazorPages();
}

次のデリゲート ハンドラーでは、IOperationScoped を使用して、送信要求用の X-OPERATION-ID ヘッダーを設定します。

public class OperationHandler : DelegatingHandler
{
    private readonly IOperationScoped _operationService;

    public OperationHandler(IOperationScoped operationScoped)
    {
        _operationService = operationScoped;
    }

    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);

        return await base.SendAsync(request, cancellationToken);
    }
}

HttpRequestsSample ダウンロード] で、/Operation に移動してページを更新します。 要求スコープの値は要求ごとに変更されますが、ハンドラーのスコープの値は 5 秒ごとに変更されるだけです。

ハンドラーは、任意のスコープのサービスに依存することができます。 ハンドラーが依存するサービスは、ハンドラーが破棄されるときに破棄されます。

次の方法のいずれかを使って、要求ごとの状態をメッセージ ハンドラーと共有します。

Polly ベースのハンドラーを使用する

IHttpClientFactory は、サードパーティのライブラリ Polly と統合します。 Polly は、.NET 用の包括的な回復力および一時的エラー処理ライブラリです。 開発者は、自然でスレッド セーフな方法を使用して、Retry、Circuit Breaker、Timeout、Bulkhead Isolation、Fallback などのポリシーを表現できます。

構成されている HttpClient インスタンスで Polly ポリシーを使用できるようにするための、拡張メソッドが提供されています。 Polly の拡張機能では、クライアントへの Polly ベースのハンドラーの追加がサポートされています。 Polly では、Microsoft.Extensions.Http.Polly NuGet パッケージが必要です。

一時的な障害を処理する

通常、外部 HTTP を呼び出すときに発生する障害は、一時的なものです。 AddTransientHttpErrorPolicy では、一時的なエラーを処理するためのポリシーを定義できます。 AddTransientHttpErrorPolicy で構成されたポリシーでは、次の応答が処理されます。

AddTransientHttpErrorPolicy では、可能性のある一時的障害を表すエラーを処理するために構成された PolicyBuilder オブジェクトへのアクセスが提供されます。

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient<UnreliableEndpointCallerService>()
        .AddTransientHttpErrorPolicy(p => 
            p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

    // Remaining code deleted for brevity.

上記のコードでは、WaitAndRetryAsync ポリシーが定義されています。 失敗した要求は最大 3 回再試行され、再試行の間には 600 ミリ秒の遅延が設けられます。

ポリシーを動的に選択する

Polly ベースのハンドラーを追加するための拡張メソッドが提供されています (AddPolicyHandler など)。 次の AddPolicyHandler のオーバーロードでは、要求が検査されて、適用するポリシーが決定されます。

var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
    .AddPolicyHandler(request => 
        request.Method == HttpMethod.Get ? timeout : longTimeout);

上記のコードでは、送信要求が HTTP GET の場合は、10 秒のタイムアウトが適用されます。 他の HTTP メソッドの場合は、30 秒のタイムアウトが使用されます。

複数の Polly ハンドラーを追加する

Polly のポリシーは入れ子にするのが一般的です。

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

前の例の場合:

  • 2 つのハンドラーが追加されます。
  • 1 つ目のハンドラーでは、AddTransientHttpErrorPolicy を使用して再試行ポリシーが追加されます。 失敗した要求は 3 回まで再試行されます。
  • 2 つ目の AddTransientHttpErrorPolicy の呼び出しでは、サーキット ブレーカー ポリシーが追加されます。 試行が連続して 5 回失敗した場合、それ以上の外部要求は 30 秒間ブロックされます。 サーキット ブレーカー ポリシーはステートフルです。 このクライアントからのすべての呼び出しは、同じサーキット状態を共有します。

Polly レジストリからポリシーを追加する

定期的に使用されるポリシーを管理するには、ポリシーを 1 回定義した後、PolicyRegistry でポリシーを登録します。

次のコードの内容は以下のとおりです。

  • "regular" ポリシーと "long" ポリシーが追加されます。
  • AddPolicyHandlerFromRegistry では、レジストリから "regular" ポリシーと "long" ポリシーが追加されます。
public void ConfigureServices(IServiceCollection services)
{           
    var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
        TimeSpan.FromSeconds(10));
    var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
        TimeSpan.FromSeconds(30));
    
    var registry = services.AddPolicyRegistry();

    registry.Add("regular", timeout);
    registry.Add("long", longTimeout);
    
    services.AddHttpClient("regularTimeoutHandler")
        .AddPolicyHandlerFromRegistry("regular");

    services.AddHttpClient("longTimeoutHandler")
       .AddPolicyHandlerFromRegistry("long");

    // Remaining code deleted for brevity.

IHttpClientFactory と Polly の統合の詳細については、Polly に関する wiki を参照してください。

HttpClient と有効期間の管理

IHttpClientFactoryCreateClient を呼び出すたびに、HttpClient の新しいインスタンスが返されます。 HttpMessageHandler は、名前付きクライアントごとに作成されます。 ファクトリによって HttpMessageHandler インスタンスの有効期間が管理されます。

IHttpClientFactory は、リソースの消費量を減らすため、ファクトリによって作成された HttpMessageHandler のインスタンスをプールします。 新しい HttpClient インスタンスを作成するときに、プールの HttpMessageHandler インスタンスの有効期間が切れていない場合はそれを再利用する場合があります。

通常、各ハンドラーでは基になる HTTP 接続が独自に管理されるため、ハンドラーはプールすることが望まれます。 必要以上のハンドラーを作成すると、接続に遅延が発生する可能性があります。 また、一部のハンドラーでは接続が無期限に開かれており、DNS (ドメイン ネーム システム) の変更にハンドラーが対応できないことがあります。

ハンドラーの既定の有効期間は 2 分です。 名前付きクライアントごとに、既定値をオーバーライドすることができます。

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient("extendedhandlerlifetime")
        .SetHandlerLifetime(TimeSpan.FromMinutes(5));

    // Remaining code deleted for brevity.

通常、HttpClient インスタンスは、破棄する必要の ない .NET オブジェクトとして扱うことができます。 破棄すると送信要求がキャンセルされ、Dispose の呼び出し後には指定された HttpClient インスタンスを使用できなくなります。 IHttpClientFactory は、HttpClient インスタンスによって使用されたリソースの追跡と破棄を行います。

IHttpClientFactory の登場以前は、1 つの HttpClient インスタンスを長い期間使い続けるパターンが一般的に使用されていました。 IHttpClientFactory に移行した後は、このパターンは不要になります。

IHttpClientFactory の代替手段

DI 対応のアプリ内で IHttpClientFactory を使用すれば、次のことを回避できます。

  • HttpMessageHandler インスタンスをプールすることによるリソース枯渇の問題。
  • 一定の間隔で HttpMessageHandler インスタンスを循環させることによって発生する古くなった DNS の問題。

有効期間の長い SocketsHttpHandler インスタンスを使用して、上記の問題を解決する別の方法があります。

  • アプリの起動時に SocketsHttpHandler インスタンスを作成し、アプリの有効期間中、それを使用します。
  • DNS の更新時間に基づいて、PooledConnectionLifetime を適切な値に構成します。
  • 必要に応じて new HttpClient(handler, disposeHandler: false) を使用して HttpClient インスタンスを作成します。

上記の方法を使用すると、IHttpClientFactory が同様の方法で解決するリソース管理の問題を解決できます。

  • SocketsHttpHandler を使用すると、HttpClient インスタンス間で接続を共有できます。 この共有によってソケットの枯渇が防止されます。
  • SocketsHttpHandler では、古くなった DNS の問題を回避するために PooledConnectionLifetime に従って接続を循環されます。

Cookies

HttpMessageHandler インスタンスをプールすると、CookieContainer オブジェクトが共有されます。 予期せぬ CookieContainer オブジェクト共有があると、多くの場合、コードは不適切なものとなります。 cookie を必要とするアプリの場合は、次のいずれかを検討してください。

  • 自動的な cookie 処理の無効化
  • IHttpClientFactory の回避

ConfigurePrimaryHttpMessageHandler を呼び出して、自動的な cookie 処理を無効にします。

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            UseCookies = false,
        };
    });

ログの記録

IHttpClientFactory によって作成されたクライアントは、すべての要求のログ メッセージを記録します。 既定のログ メッセージを見るには、ログの構成で適切な情報レベルを有効にします。 要求ヘッダーのログなどの追加ログは、トレース レベルでのみ含まれます。

各クライアントに使用されるログのカテゴリには、クライアントの名前が含まれます。 たとえば、MyNamedClient という名前のクライアントでは、"System.Net.Http.HttpClient.MyNamedClient.LogicalHandler" というカテゴリでメッセージがログに記録されます。 LogicalHandler というサフィックスが付いたメッセージが、要求ハンドラーのパイプラインの外部で発生します。 要求では、パイプライン内の他のハンドラーが要求を処理する前に、メッセージがログに記録されます。 応答では、他のパイプライン ハンドラーが応答を受け取った後で、メッセージがログに記録されます。

ログ記録は、要求ハンドラーのパイプラインの内部でも行われます。 MyNamedClient の例では、これらのメッセージはログ カテゴリ "System.Net.Http.HttpClient.MyNamedClient.ClientHandler" でログに記録されます。 要求では、他のすべてのハンドラーが実行された後、要求が送信される直前に、これが行われます。 応答では、このログ記録には、ハンドラー パイプラインを通じ戻される前の応答の状態が含まれます。

パイプラインの外部と内部でログを有効にすると、他のパイプライン ハンドラーによる変更を検査できます。 これには、要求ヘッダーや応答状態コードへの変更を含めることができます。

ログのカテゴリにクライアントの名前を含めると、特定の名前付きクライアントでログをフィルター処理できます。

HttpMessageHandler を構成する

クライアントによって使用される内部 HttpMessageHandler の構成を制御することが必要な場合があります。

名前付きクライアントまたは型指定されたクライアントを追加すると、IHttpClientBuilder が返されます。 ConfigurePrimaryHttpMessageHandler 拡張メソッドを使用して、デリゲートを定義することができます。 デリゲートは、そのクライアントによって使用されるプライマリ HttpMessageHandler の作成と構成に使用されます。

public void ConfigureServices(IServiceCollection services)
{            
    services.AddHttpClient("configured-inner-handler")
        .ConfigurePrimaryHttpMessageHandler(() =>
        {
            return new HttpClientHandler()
            {
                AllowAutoRedirect = false,
                UseDefaultCredentials = true
            };
        });

    // Remaining code deleted for brevity.

コンソール アプリで IHttpClientFactory を使用する

コンソール アプリで、次のパッケージ参照をプロジェクトに追加します。

次に例を示します。

  • IHttpClientFactory汎用ホストのサービス コンテナーに登録されます。
  • MyService により、クライアント ファクトリ インスタンスがサービスから作成され、それが HttpClient の作成に使用されます。 HttpClient は Web ページを取得する目的で使用されます。
  • Main により、サービスの GetPage メソッドを実行し、Web ページ コンテンツの最初の 500 文字をコンソールに書き込むためのスコープが作成されます。
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
    static async Task<int> Main(string[] args)
    {
        var builder = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHttpClient();
                services.AddTransient<IMyService, MyService>();
            }).UseConsoleLifetime();

        var host = builder.Build();

        try
        {
            var myService = host.Services.GetRequiredService<IMyService>();
            var pageContent = await myService.GetPage();

            Console.WriteLine(pageContent.Substring(0, 500));
        }
        catch (Exception ex)
        {
            var logger = host.Services.GetRequiredService<ILogger<Program>>();

            logger.LogError(ex, "An error occurred.");
        }

        return 0;
    }

    public interface IMyService
    {
        Task<string> GetPage();
    }

    public class MyService : IMyService
    {
        private readonly IHttpClientFactory _clientFactory;

        public MyService(IHttpClientFactory clientFactory)
        {
            _clientFactory = clientFactory;
        }

        public async Task<string> GetPage()
        {
            // Content from BBC One: Dr. Who website (©BBC)
            var request = new HttpRequestMessage(HttpMethod.Get,
                "https://www.bbc.co.uk/programmes/b006q2x0");
            var client = _clientFactory.CreateClient();
            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                return $"StatusCode: {response.StatusCode}";
            }
        }
    }
}

ヘッダー伝達ミドルウェア

ヘッダー伝達は、受信要求から送信 HTTP クライアント要求に HTTP ヘッダーを伝達するための ASP.NET Core ミドルウェアです。 ヘッダー伝達を使用するには、次を行います。

  • Microsoft.AspNetCore.HeaderPropagation パッケージを参照します。

  • Startup でミドルウェアと HttpClient を構成します。

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    
        services.AddHttpClient("MyForwardingClient").AddHeaderPropagation();
        services.AddHeaderPropagation(options =>
        {
            options.Headers.Add("X-TraceId");
        });
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseHttpsRedirection();
    
        app.UseHeaderPropagation();
    
        app.UseRouting();
    
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    
  • クライアントで、構成されたヘッダーが送信要求に含まれます。

    var client = clientFactory.CreateClient("MyForwardingClient");
    var response = client.GetAsync(...);
    

その他の技術情報

作成者: Kirk LarkinSteve GordonGlenn CondronRyan Nowak

アプリ内で HttpClient インスタンスを構成して作成するために、IHttpClientFactory を登録して使用できます。 IHttpClientFactory には次のような利点があります。

  • 論理 HttpClient インスタンスの名前付けと構成を一元化します。 たとえば、GitHub にアクセスするために、github という名前のクライアントを登録して構成できます。 一般的なアクセスのために、既定のクライアントを登録できます。
  • HttpClient でのハンドラーのデリゲートにより、送信ミドルウェアの概念が体系化されます。 HttpClient でのハンドラーのデリゲートを利用するために、Polly ベースのミドルウェアに対する拡張機能が提供されます。
  • 基になっている HttpClientMessageHandler インスタンスのプールと有効期間が管理されます。 自動管理により、HttpClient の有効期間を手動で管理するときの一般的な DNS (ドメイン ネーム システム) の問題が発生しなくなります。
  • ファクトリによって作成されたクライアントから送信されるすべての要求に対し、(ILogger によって) 構成可能なログ エクスペリエンスを追加します。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

このトピックのバージョンのサンプル コードでは、HTTP 応答で返された JSON コンテンツを、System.Text.Json を使用して逆シリアル化します。 Json.NET および ReadAsAsync<T> を使用するサンプルについては、バージョン セレクターを使用して、このトピックの 2.x バージョンを選択してください。

利用パターン

アプリで IHttpClientFactory を使用するには複数の方法があります。

どの方法が最善かは、アプリの要件によって異なります。

基本的な使用方法

IHttpClientFactory は、AddHttpClient を呼び出すことによって登録できます。

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpClient();
        // Remaining code deleted for brevity.

IHttpClientFactory は、依存関係の挿入 (DI) を使用して要求できます。 次のコードでは、IHttpClientFactory を使用して HttpClient インスタンスを作成しています。

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubBranch> Branches { get; private set; }

    public bool GetBranchesError { get; private set; }

    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            Branches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(responseStream);
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }
    }
}

前の例のように IHttpClientFactory を使用するのは、既存のアプリをリファクタリングするのに適した方法です。 HttpClient の使用方法に対する影響はありません。 既存のアプリで HttpClient のインスタンスが作成されている場所を、CreateClient の呼び出しに置き換えます。

名前付きクライアント

名前付きクライアントは、次の場合に適しています。

  • アプリで、多くの異なる HttpClient を使用する必要がある。
  • 多くの HttpClient の構成が異なる。

名前付き HttpClient の構成は、Startup.ConfigureServices での登録時に指定できます。

services.AddHttpClient("github", c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    // Github API versioning
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    // Github requires a user-agent
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

上記のコードでは、クライアントに次のものが構成されます。

  • ベース アドレス https://api.github.com/
  • GitHub API を使用するために必要な 2 つのヘッダー。

CreateClient

CreateClient が呼び出されるたびに、次のことが行われます。

  • HttpClient の新しいインスタンスが作成されます。
  • 構成アクションが呼び出されます。

名前付きクライアントを作成するには、その名前を CreateClient に渡します。

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }

    public bool GetPullRequestsError { get; private set; }

    public bool HasPullRequests => PullRequests.Any();

    public NamedClientModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "repos/dotnet/AspNetCore.Docs/pulls");

        var client = _clientFactory.CreateClient("github");

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            PullRequests = await JsonSerializer.DeserializeAsync
                    <IEnumerable<GitHubPullRequest>>(responseStream);
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}

上記のコードでは、要求でホスト名を指定する必要はありません。 クライアントに構成されているベース アドレスが使用されるため、コードではパスを渡すだけで済みます。

型指定されたクライアント

型指定されたクライアント:

  • キーとして文字列を使用する必要なしに、名前付きクライアントと同じ機能を提供します。
  • クライアントを使用するときに、IntelliSense とコンパイラのヘルプが提供されます。
  • 特定の HttpClient を構成してそれと対話する 1 つの場所を提供します。 たとえば、単一の型指定されたクライアントは、次のために使用される場合があります。
    • 単一のバックエンド エンドポイント用。
    • エンドポイントを処理するすべてのロジックをカプセル化するため。
  • DI に対応しており、アプリ内の必要な場所に挿入できます。

型指定されたクライアントは、そのコンストラクターで HttpClient パラメーターを受け取ります。

public class GitHubService
{
    public HttpClient Client { get; }

    public GitHubService(HttpClient client)
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        // GitHub API versioning
        client.DefaultRequestHeaders.Add("Accept",
            "application/vnd.github.v3+json");
        // GitHub requires a user-agent
        client.DefaultRequestHeaders.Add("User-Agent",
            "HttpClientFactory-Sample");

        Client = client;
    }

    public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
    {
        return await Client.GetFromJsonAsync<IEnumerable<GitHubIssue>>(
          "/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
    }
}

上のコードでは以下の操作が行われます。

  • 構成が型指定されたクライアントに移動されます。
  • HttpClient オブジェクトは、パブリック プロパティとして公開されます。

HttpClient の機能を公開する API 固有のメソッドを作成できます。 たとえば、GetAspNetDocsIssues メソッドでは、未解決の問題を取得するためのコードがカプセル化されています。

次のコードでは、Startup.ConfigureServices 内で AddHttpClient を呼び出して、型指定されたクライアント クラスを登録しています。

services.AddHttpClient<GitHubService>();

型指定されたクライアントは、DI で一時的として登録されます。 上記のコードで、AddHttpClientGitHubService を一時的なサービスとして登録します。 この登録では、ファクトリ メソッドを使用して次のことを行います。

  1. HttpClient のインスタンスを作成します。
  2. GitHubService のインスタンスを作成し、HttpClient のインスタンスをそのコンストラクターに渡します。

型指定されたクライアントは、直接挿入して使用できます。

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;

    public IEnumerable<GitHubIssue> LatestIssues { get; private set; }

    public bool HasIssue => LatestIssues.Any();

    public bool GetIssuesError { get; private set; }

    public TypedClientModel(GitHubService gitHubService)
    {
        _gitHubService = gitHubService;
    }

    public async Task OnGet()
    {
        try
        {
            LatestIssues = await _gitHubService.GetAspNetDocsIssues();
        }
        catch(HttpRequestException)
        {
            GetIssuesError = true;
            LatestIssues = Array.Empty<GitHubIssue>();
        }
    }
}

型指定されたクライアントのコンストラクターではなく、Startup.ConfigureServices での登録時に型指定されたクライアントの構成を指定できます。

services.AddHttpClient<RepoService>(c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

HttpClient は、型指定されたクライアント内にカプセル化できます。 プロパティとして公開するのではなく、HttpClient インスタンスを内部的に呼び出すパブリック メソッドを定義します。

public class RepoService
{
    // _httpClient isn't exposed publicly
    private readonly HttpClient _httpClient;

    public RepoService(HttpClient client)
    {
        _httpClient = client;
    }

    public async Task<IEnumerable<string>> GetRepos()
    {
        var response = await _httpClient.GetAsync("aspnet/repos");

        response.EnsureSuccessStatusCode();

        using var responseStream = await response.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync
            <IEnumerable<string>>(responseStream);
    }
}

上記のコードでは、HttpClient はプライベート フィールドに格納されます。 HttpClient へのアクセスは、パブリック GetRepos メソッドによって行われます。

生成されたクライアント

IHttpClientFactory は、Refit などのサードパーティ製ライブラリと組み合わせて使用できます。 Refit は、.NET 用の REST ライブラリです。 REST API をライブ インターフェイスに変換します。 インターフェイスの実装は RestService によって動的に生成され、HttpClient を使用して外部 HTTP の呼び出しを行います。

インターフェイスと応答は、外部の API とその応答を表すように定義されます。

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

型指定されたクライアントを追加し、Refit を使用して実装を生成できます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("http://localhost:5000");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddControllers();
}

定義されたインターフェイスは、DI および Refit によって提供される実装で必要に応じて使用できます。

[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IHelloClient _client;

    public ValuesController(IHelloClient client)
    {
        _client = client;
    }

    [HttpGet("/")]
    public async Task<ActionResult<Reply>> Index()
    {
        return await _client.GetMessageAsync();
    }
}

POST、PUT、DELETE の要求を行う

前の例では、すべての HTTP 要求で GET HTTP 動詞が使用されています。 HttpClient では、次のような他の HTTP 動詞もサポートされています。

  • POST
  • PUT
  • Del
  • PATCH

サポートされている HTTP 動詞の一覧については、「HttpMethod」を参照してください。

次の例は、HTTP POST 要求の方法を示しています。

public async Task CreateItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
        Encoding.UTF8,
        "application/json");

    using var httpResponse =
        await _httpClient.PostAsync("/api/TodoItems", todoItemJson);

    httpResponse.EnsureSuccessStatusCode();
}

上記のコードの CreateItemAsync メソッドは:

  • System.Text.Json を使用して TodoItem パラメーターを JSON にシリアル化します。 これは、JsonSerializerOptions のインスタンスを使用して、シリアル化プロセスを構成します。
  • HTTP 要求の本文で送信するためにシリアル化された JSON をパッケージ化する StringContent のインスタンスを作成します。
  • PostAsync を呼び出して、指定した URL に JSON の内容を送信します。 これは HttpClient.BaseAddress に追加される相対 URL です。
  • 応答状態コードが成功を示していない場合に、EnsureSuccessStatusCode を呼び出して例外をスローします。

HttpClient は、他の種類のコンテンツもサポートしています。 たとえば、MultipartContentStreamContent です。 サポートされているコンテンツの一覧については、「HttpContent」を参照してください。

HTTP PUT 要求の例を次に示します。

public async Task SaveItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem),
        Encoding.UTF8,
        "application/json");

    using var httpResponse =
        await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);

    httpResponse.EnsureSuccessStatusCode();
}

上記のコードは、POST の例によく似ています。 SaveItemAsync メソッドは PostAsync ではなく PutAsync を呼び出します。

HTTP DELETE 要求の例を次に示します。

public async Task DeleteItemAsync(long itemId)
{
    using var httpResponse =
        await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");

    httpResponse.EnsureSuccessStatusCode();
}

上記のコードの DeleteItemAsync メソッドは DeleteAsync を呼び出します。 HTTP DELETE 要求には通常、本文が含まれていないため、DeleteAsync メソッドは HttpContent のインスタンスを受け入れるオーバーロードを提供しません。

HttpClient でのさまざまな HTTP 動詞の使用の詳細については、「HttpClient」を参照してください。

送信要求ミドルウェア

HttpClient は、送信 HTTP 要求用にリンクできるハンドラーのデリゲートの概念を備えています。 IHttpClientFactory:

  • 各名前付きクライアントに適用するハンドラーの定義が簡単になります。

  • 送信要求ミドルウェア パイプラインを構築するための複数のハンドラーの登録とチェーン化がサポートされています。 これらの各ハンドラーでは、送信要求の前と後に処理を実行できます。 このパターンは次のようなものです。

    • ASP.NET Core での受信ミドルウェア パイプラインに似ています。
    • 次のような HTTP 要求に関する横断的な問題を管理するためのメカニズムが提供されます。
      • キャッシュ
      • エラー処理
      • シリアル化
      • ログ

デリゲート ハンドラーを作成するには、次のようにします。

  • DelegatingHandler から派生します。
  • SendAsync をオーバーライドします。 パイプライン内の次のハンドラーに要求を渡す前に、コードを実行します。
public class ValidateHeaderHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (!request.Headers.Contains("X-API-KEY"))
        {
            return new HttpResponseMessage(HttpStatusCode.BadRequest)
            {
                Content = new StringContent(
                    "You must supply an API key header called X-API-KEY")
            };
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

上記のコードでは、X-API-KEY ヘッダーが要求に含まれているかどうかが確認されます。 X-API-KEY がない場合は、BadRequest が返されます。

Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler を使用して HttpClient の構成に複数のハンドラーを追加できます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<ValidateHeaderHandler>();

    services.AddHttpClient("externalservice", c =>
    {
        // Assume this is an "external" service which requires an API KEY
        c.BaseAddress = new Uri("https://localhost:5001/");
    })
    .AddHttpMessageHandler<ValidateHeaderHandler>();

    // Remaining code deleted for brevity.

上記のコードでは、ValidateHeaderHandler が DI で登録されます。 登録が済むと、AddHttpMessageHandler を呼び出してハンドラーの型を渡すことができます。

実行する順序で、複数のハンドラーを登録することができます。 最後の HttpClientHandler が要求を実行するまで、各ハンドラーは次のハンドラーをラップします。

services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();

services.AddHttpClient("clientwithhandlers")
    // This handler is on the outside and called first during the 
    // request, last during the response.
    .AddHttpMessageHandler<SecureRequestHandler>()
    // This handler is on the inside, closest to the request being 
    // sent.
    .AddHttpMessageHandler<RequestDataHandler>();

送信要求ミドルウェアで DI を使用する

IHttpClientFactory によって新しいデリゲート ハンドラーが作成される際、ハンドラーのコンストラクター パラメーターを満たすために DI が使用されます。 IHttpClientFactory により、ハンドラーごとに 個別の DI スコープが作成されます。これにより、ハンドラーによって "スコープ付き" サービスが使用されるときに、予期しない動作が発生する可能性があります。

たとえば、識別子 OperationId を持つ操作としてタスクを表す、次のインターフェイスとその実装について考えてみます。

public interface IOperationScoped 
{
    string OperationId { get; }
}

public class OperationScoped : IOperationScoped
{
    public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

その名前が示すように、IOperationScoped は "スコープ付き" 有効期間を使用して DI に登録されます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<TodoContext>(options =>
        options.UseInMemoryDatabase("TodoItems"));

    services.AddHttpContextAccessor();

    services.AddHttpClient<TodoClient>((sp, httpClient) =>
    {
        var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;

        // For sample purposes, assume TodoClient is used in the context of an incoming request.
        httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
                                         httpRequest.Host, httpRequest.PathBase));
        httpClient.Timeout = TimeSpan.FromSeconds(5);
    });

    services.AddScoped<IOperationScoped, OperationScoped>();
    
    services.AddTransient<OperationHandler>();
    services.AddTransient<OperationResponseHandler>();

    services.AddHttpClient("Operation")
        .AddHttpMessageHandler<OperationHandler>()
        .AddHttpMessageHandler<OperationResponseHandler>()
        .SetHandlerLifetime(TimeSpan.FromSeconds(5));

    services.AddControllers();
    services.AddRazorPages();
}

次のデリゲート ハンドラーでは、IOperationScoped を使用して、送信要求用の X-OPERATION-ID ヘッダーを設定します。

public class OperationHandler : DelegatingHandler
{
    private readonly IOperationScoped _operationService;

    public OperationHandler(IOperationScoped operationScoped)
    {
        _operationService = operationScoped;
    }

    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);

        return await base.SendAsync(request, cancellationToken);
    }
}

HttpRequestsSample ダウンロード] で、/Operation に移動してページを更新します。 要求スコープの値は要求ごとに変更されますが、ハンドラーのスコープの値は 5 秒ごとに変更されるだけです。

ハンドラーは、任意のスコープのサービスに依存することができます。 ハンドラーが依存するサービスは、ハンドラーが破棄されるときに破棄されます。

次の方法のいずれかを使って、要求ごとの状態をメッセージ ハンドラーと共有します。

Polly ベースのハンドラーを使用する

IHttpClientFactory は、サードパーティのライブラリ Polly と統合します。 Polly は、.NET 用の包括的な回復力および一時的エラー処理ライブラリです。 開発者は、自然でスレッド セーフな方法を使用して、Retry、Circuit Breaker、Timeout、Bulkhead Isolation、Fallback などのポリシーを表現できます。

構成されている HttpClient インスタンスで Polly ポリシーを使用できるようにするための、拡張メソッドが提供されています。 Polly の拡張機能では、クライアントへの Polly ベースのハンドラーの追加がサポートされています。 Polly では、Microsoft.Extensions.Http.Polly NuGet パッケージが必要です。

一時的な障害を処理する

通常、外部 HTTP を呼び出すときに発生する障害は、一時的なものです。 AddTransientHttpErrorPolicy では、一時的なエラーを処理するためのポリシーを定義できます。 AddTransientHttpErrorPolicy で構成されたポリシーでは、次の応答が処理されます。

AddTransientHttpErrorPolicy では、可能性のある一時的障害を表すエラーを処理するために構成された PolicyBuilder オブジェクトへのアクセスが提供されます。

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient<UnreliableEndpointCallerService>()
        .AddTransientHttpErrorPolicy(p => 
            p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

    // Remaining code deleted for brevity.

上記のコードでは、WaitAndRetryAsync ポリシーが定義されています。 失敗した要求は最大 3 回再試行され、再試行の間には 600 ミリ秒の遅延が設けられます。

ポリシーを動的に選択する

Polly ベースのハンドラーを追加するための拡張メソッドが提供されています (AddPolicyHandler など)。 次の AddPolicyHandler のオーバーロードでは、要求が検査されて、適用するポリシーが決定されます。

var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
    .AddPolicyHandler(request => 
        request.Method == HttpMethod.Get ? timeout : longTimeout);

上記のコードでは、送信要求が HTTP GET の場合は、10 秒のタイムアウトが適用されます。 他の HTTP メソッドの場合は、30 秒のタイムアウトが使用されます。

複数の Polly ハンドラーを追加する

Polly のポリシーは入れ子にするのが一般的です。

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

前の例の場合:

  • 2 つのハンドラーが追加されます。
  • 1 つ目のハンドラーでは、AddTransientHttpErrorPolicy を使用して再試行ポリシーが追加されます。 失敗した要求は 3 回まで再試行されます。
  • 2 つ目の AddTransientHttpErrorPolicy の呼び出しでは、サーキット ブレーカー ポリシーが追加されます。 試行が連続して 5 回失敗した場合、それ以上の外部要求は 30 秒間ブロックされます。 サーキット ブレーカー ポリシーはステートフルです。 このクライアントからのすべての呼び出しは、同じサーキット状態を共有します。

Polly レジストリからポリシーを追加する

定期的に使用されるポリシーを管理するには、ポリシーを 1 回定義した後、PolicyRegistry でポリシーを登録します。

次のコードの内容は以下のとおりです。

  • "regular" ポリシーと "long" ポリシーが追加されます。
  • AddPolicyHandlerFromRegistry では、レジストリから "regular" ポリシーと "long" ポリシーが追加されます。
public void ConfigureServices(IServiceCollection services)
{           
    var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
        TimeSpan.FromSeconds(10));
    var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
        TimeSpan.FromSeconds(30));
    
    var registry = services.AddPolicyRegistry();

    registry.Add("regular", timeout);
    registry.Add("long", longTimeout);
    
    services.AddHttpClient("regularTimeoutHandler")
        .AddPolicyHandlerFromRegistry("regular");

    services.AddHttpClient("longTimeoutHandler")
       .AddPolicyHandlerFromRegistry("long");

    // Remaining code deleted for brevity.

IHttpClientFactory と Polly の統合の詳細については、Polly に関する wiki を参照してください。

HttpClient と有効期間の管理

IHttpClientFactoryCreateClient を呼び出すたびに、HttpClient の新しいインスタンスが返されます。 HttpMessageHandler は、名前付きクライアントごとに作成されます。 ファクトリによって HttpMessageHandler インスタンスの有効期間が管理されます。

IHttpClientFactory は、リソースの消費量を減らすため、ファクトリによって作成された HttpMessageHandler のインスタンスをプールします。 新しい HttpClient インスタンスを作成するときに、プールの HttpMessageHandler インスタンスの有効期間が切れていない場合はそれを再利用する場合があります。

通常、各ハンドラーでは基になる HTTP 接続が独自に管理されるため、ハンドラーはプールすることが望まれます。 必要以上のハンドラーを作成すると、接続に遅延が発生する可能性があります。 また、一部のハンドラーでは接続が無期限に開かれており、DNS (ドメイン ネーム システム) の変更にハンドラーが対応できないことがあります。

ハンドラーの既定の有効期間は 2 分です。 名前付きクライアントごとに、既定値をオーバーライドすることができます。

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient("extendedhandlerlifetime")
        .SetHandlerLifetime(TimeSpan.FromMinutes(5));

    // Remaining code deleted for brevity.

通常、HttpClient インスタンスは、破棄する必要の ない .NET オブジェクトとして扱うことができます。 破棄すると送信要求がキャンセルされ、Dispose の呼び出し後には指定された HttpClient インスタンスを使用できなくなります。 IHttpClientFactory は、HttpClient インスタンスによって使用されたリソースの追跡と破棄を行います。

IHttpClientFactory の登場以前は、1 つの HttpClient インスタンスを長い期間使い続けるパターンが一般的に使用されていました。 IHttpClientFactory に移行した後は、このパターンは不要になります。

IHttpClientFactory の代替手段

DI 対応のアプリ内で IHttpClientFactory を使用すれば、次のことを回避できます。

  • HttpMessageHandler インスタンスをプールすることによるリソース枯渇の問題。
  • 一定の間隔で HttpMessageHandler インスタンスを循環させることによって発生する古くなった DNS の問題。

有効期間の長い SocketsHttpHandler インスタンスを使用して、上記の問題を解決する別の方法があります。

  • アプリの起動時に SocketsHttpHandler インスタンスを作成し、アプリの有効期間中、それを使用します。
  • DNS の更新時間に基づいて、PooledConnectionLifetime を適切な値に構成します。
  • 必要に応じて new HttpClient(handler, disposeHandler: false) を使用して HttpClient インスタンスを作成します。

上記の方法を使用すると、IHttpClientFactory が同様の方法で解決するリソース管理の問題を解決できます。

  • SocketsHttpHandler を使用すると、HttpClient インスタンス間で接続を共有できます。 この共有によってソケットの枯渇が防止されます。
  • SocketsHttpHandler では、古くなった DNS の問題を回避するために PooledConnectionLifetime に従って接続を循環されます。

Cookies

HttpMessageHandler インスタンスをプールすると、CookieContainer オブジェクトが共有されます。 予期せぬ CookieContainer オブジェクト共有があると、多くの場合、コードは不適切なものとなります。 cookie を必要とするアプリの場合は、次のいずれかを検討してください。

  • 自動的な cookie 処理の無効化
  • IHttpClientFactory の回避

ConfigurePrimaryHttpMessageHandler を呼び出して、自動的な cookie 処理を無効にします。

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            UseCookies = false,
        };
    });

ログの記録

IHttpClientFactory によって作成されたクライアントは、すべての要求のログ メッセージを記録します。 既定のログ メッセージを見るには、ログの構成で適切な情報レベルを有効にします。 要求ヘッダーのログなどの追加ログは、トレース レベルでのみ含まれます。

各クライアントに使用されるログのカテゴリには、クライアントの名前が含まれます。 たとえば、MyNamedClient という名前のクライアントでは、"System.Net.Http.HttpClient.MyNamedClient.LogicalHandler" というカテゴリでメッセージがログに記録されます。 LogicalHandler というサフィックスが付いたメッセージが、要求ハンドラーのパイプラインの外部で発生します。 要求では、パイプライン内の他のハンドラーが要求を処理する前に、メッセージがログに記録されます。 応答では、他のパイプライン ハンドラーが応答を受け取った後で、メッセージがログに記録されます。

ログ記録は、要求ハンドラーのパイプラインの内部でも行われます。 MyNamedClient の例では、これらのメッセージはログ カテゴリ "System.Net.Http.HttpClient.MyNamedClient.ClientHandler" でログに記録されます。 要求では、他のすべてのハンドラーが実行された後、要求が送信される直前に、これが行われます。 応答では、このログ記録には、ハンドラー パイプラインを通じ戻される前の応答の状態が含まれます。

パイプラインの外部と内部でログを有効にすると、他のパイプライン ハンドラーによる変更を検査できます。 これには、要求ヘッダーや応答状態コードへの変更を含めることができます。

ログのカテゴリにクライアントの名前を含めると、特定の名前付きクライアントでログをフィルター処理できます。

HttpMessageHandler を構成する

クライアントによって使用される内部 HttpMessageHandler の構成を制御することが必要な場合があります。

名前付きクライアントまたは型指定されたクライアントを追加すると、IHttpClientBuilder が返されます。 ConfigurePrimaryHttpMessageHandler 拡張メソッドを使用して、デリゲートを定義することができます。 デリゲートは、そのクライアントによって使用されるプライマリ HttpMessageHandler の作成と構成に使用されます。

public void ConfigureServices(IServiceCollection services)
{            
    services.AddHttpClient("configured-inner-handler")
        .ConfigurePrimaryHttpMessageHandler(() =>
        {
            return new HttpClientHandler()
            {
                AllowAutoRedirect = false,
                UseDefaultCredentials = true
            };
        });

    // Remaining code deleted for brevity.

コンソール アプリで IHttpClientFactory を使用する

コンソール アプリで、次のパッケージ参照をプロジェクトに追加します。

次に例を示します。

  • IHttpClientFactory汎用ホストのサービス コンテナーに登録されます。
  • MyService により、クライアント ファクトリ インスタンスがサービスから作成され、それが HttpClient の作成に使用されます。 HttpClient は Web ページを取得する目的で使用されます。
  • Main により、サービスの GetPage メソッドを実行し、Web ページ コンテンツの最初の 500 文字をコンソールに書き込むためのスコープが作成されます。
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
    static async Task<int> Main(string[] args)
    {
        var builder = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHttpClient();
                services.AddTransient<IMyService, MyService>();
            }).UseConsoleLifetime();

        var host = builder.Build();

        try
        {
            var myService = host.Services.GetRequiredService<IMyService>();
            var pageContent = await myService.GetPage();

            Console.WriteLine(pageContent.Substring(0, 500));
        }
        catch (Exception ex)
        {
            var logger = host.Services.GetRequiredService<ILogger<Program>>();

            logger.LogError(ex, "An error occurred.");
        }

        return 0;
    }

    public interface IMyService
    {
        Task<string> GetPage();
    }

    public class MyService : IMyService
    {
        private readonly IHttpClientFactory _clientFactory;

        public MyService(IHttpClientFactory clientFactory)
        {
            _clientFactory = clientFactory;
        }

        public async Task<string> GetPage()
        {
            // Content from BBC One: Dr. Who website (©BBC)
            var request = new HttpRequestMessage(HttpMethod.Get,
                "https://www.bbc.co.uk/programmes/b006q2x0");
            var client = _clientFactory.CreateClient();
            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                return $"StatusCode: {response.StatusCode}";
            }
        }
    }
}

ヘッダー伝達ミドルウェア

ヘッダー伝達は、受信要求から送信 HTTP クライアント要求に HTTP ヘッダーを伝達するための ASP.NET Core ミドルウェアです。 ヘッダー伝達を使用するには、次を行います。

  • Microsoft.AspNetCore.HeaderPropagation パッケージを参照します。

  • Startup でミドルウェアと HttpClient を構成します。

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    
        services.AddHttpClient("MyForwardingClient").AddHeaderPropagation();
        services.AddHeaderPropagation(options =>
        {
            options.Headers.Add("X-TraceId");
        });
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseHttpsRedirection();
    
        app.UseHeaderPropagation();
    
        app.UseRouting();
    
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    
  • クライアントで、構成されたヘッダーが送信要求に含まれます。

    var client = clientFactory.CreateClient("MyForwardingClient");
    var response = client.GetAsync(...);
    

その他の技術情報

作成者: Kirk LarkinSteve GordonGlenn CondronRyan Nowak

アプリ内で HttpClient インスタンスを構成して作成するために、IHttpClientFactory を登録して使用できます。 IHttpClientFactory には次のような利点があります。

  • 論理 HttpClient インスタンスの名前付けと構成を一元化します。 たとえば、GitHub にアクセスするために、github という名前のクライアントを登録して構成できます。 一般的なアクセスのために、既定のクライアントを登録できます。
  • HttpClient でのハンドラーのデリゲートにより、送信ミドルウェアの概念が体系化されます。 HttpClient でのハンドラーのデリゲートを利用するために、Polly ベースのミドルウェアに対する拡張機能が提供されます。
  • 基になっている HttpClientMessageHandler インスタンスのプールと有効期間が管理されます。 自動管理により、HttpClient の有効期間を手動で管理するときの一般的な DNS (ドメイン ネーム システム) の問題が発生しなくなります。
  • ファクトリによって作成されたクライアントから送信されるすべての要求に対し、(ILogger によって) 構成可能なログ エクスペリエンスを追加します。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

このトピックのバージョンのサンプル コードでは、HTTP 応答で返された JSON コンテンツを、System.Text.Json を使用して逆シリアル化します。 Json.NET および ReadAsAsync<T> を使用するサンプルについては、バージョン セレクターを使用して、このトピックの 2.x バージョンを選択してください。

利用パターン

アプリで IHttpClientFactory を使用するには複数の方法があります。

どの方法が最善かは、アプリの要件によって異なります。

基本的な使用方法

IHttpClientFactory は、AddHttpClient を呼び出すことによって登録できます。

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpClient();
        // Remaining code deleted for brevity.

IHttpClientFactory は、依存関係の挿入 (DI) を使用して要求できます。 次のコードでは、IHttpClientFactory を使用して HttpClient インスタンスを作成しています。

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubBranch> Branches { get; private set; }

    public bool GetBranchesError { get; private set; }

    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            Branches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(responseStream);
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }
    }
}

前の例のように IHttpClientFactory を使用するのは、既存のアプリをリファクタリングするのに適した方法です。 HttpClient の使用方法に対する影響はありません。 既存のアプリで HttpClient のインスタンスが作成されている場所を、CreateClient の呼び出しに置き換えます。

名前付きクライアント

名前付きクライアントは、次の場合に適しています。

  • アプリで、多くの異なる HttpClient を使用する必要がある。
  • 多くの HttpClient の構成が異なる。

名前付き HttpClient の構成は、Startup.ConfigureServices での登録時に指定できます。

services.AddHttpClient("github", c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    // Github API versioning
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    // Github requires a user-agent
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

上記のコードでは、クライアントに次のものが構成されます。

  • ベース アドレス https://api.github.com/
  • GitHub API を使用するために必要な 2 つのヘッダー。

CreateClient

CreateClient が呼び出されるたびに、次のことが行われます。

  • HttpClient の新しいインスタンスが作成されます。
  • 構成アクションが呼び出されます。

名前付きクライアントを作成するには、その名前を CreateClient に渡します。

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }

    public bool GetPullRequestsError { get; private set; }

    public bool HasPullRequests => PullRequests.Any();

    public NamedClientModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "repos/dotnet/AspNetCore.Docs/pulls");

        var client = _clientFactory.CreateClient("github");

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            PullRequests = await JsonSerializer.DeserializeAsync
                    <IEnumerable<GitHubPullRequest>>(responseStream);
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}

上記のコードでは、要求でホスト名を指定する必要はありません。 クライアントに構成されているベース アドレスが使用されるため、コードではパスを渡すだけで済みます。

型指定されたクライアント

型指定されたクライアント:

  • キーとして文字列を使用する必要なしに、名前付きクライアントと同じ機能を提供します。
  • クライアントを使用するときに、IntelliSense とコンパイラのヘルプが提供されます。
  • 特定の HttpClient を構成してそれと対話する 1 つの場所を提供します。 たとえば、単一の型指定されたクライアントは、次のために使用される場合があります。
    • 単一のバックエンド エンドポイント用。
    • エンドポイントを処理するすべてのロジックをカプセル化するため。
  • DI に対応しており、アプリ内の必要な場所に挿入できます。

型指定されたクライアントは、そのコンストラクターで HttpClient パラメーターを受け取ります。

public class GitHubService
{
    public HttpClient Client { get; }

    public GitHubService(HttpClient client)
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        // GitHub API versioning
        client.DefaultRequestHeaders.Add("Accept",
            "application/vnd.github.v3+json");
        // GitHub requires a user-agent
        client.DefaultRequestHeaders.Add("User-Agent",
            "HttpClientFactory-Sample");

        Client = client;
    }

    public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
    {
        var response = await Client.GetAsync(
            "/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

        using var responseStream = await response.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync
            <IEnumerable<GitHubIssue>>(responseStream);
    }
}

コードのコメントを英語以外の言語に翻訳し表示したい場合、こちらの GitHub ディスカッション イシューにてお知らせください。

上のコードでは以下の操作が行われます。

  • 構成が型指定されたクライアントに移動されます。
  • HttpClient オブジェクトは、パブリック プロパティとして公開されます。

HttpClient の機能を公開する API 固有のメソッドを作成できます。 たとえば、GetAspNetDocsIssues メソッドでは、未解決の問題を取得するためのコードがカプセル化されています。

次のコードでは、Startup.ConfigureServices 内で AddHttpClient を呼び出して、型指定されたクライアント クラスを登録しています。

services.AddHttpClient<GitHubService>();

型指定されたクライアントは、DI で一時的として登録されます。 上記のコードで、AddHttpClientGitHubService を一時的なサービスとして登録します。 この登録では、ファクトリ メソッドを使用して次のことを行います。

  1. HttpClient のインスタンスを作成します。
  2. GitHubService のインスタンスを作成し、HttpClient のインスタンスをそのコンストラクターに渡します。

型指定されたクライアントは、直接挿入して使用できます。

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;

    public IEnumerable<GitHubIssue> LatestIssues { get; private set; }

    public bool HasIssue => LatestIssues.Any();

    public bool GetIssuesError { get; private set; }

    public TypedClientModel(GitHubService gitHubService)
    {
        _gitHubService = gitHubService;
    }

    public async Task OnGet()
    {
        try
        {
            LatestIssues = await _gitHubService.GetAspNetDocsIssues();
        }
        catch(HttpRequestException)
        {
            GetIssuesError = true;
            LatestIssues = Array.Empty<GitHubIssue>();
        }
    }
}

型指定されたクライアントのコンストラクターではなく、Startup.ConfigureServices での登録時に型指定されたクライアントの構成を指定できます。

services.AddHttpClient<RepoService>(c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

HttpClient は、型指定されたクライアント内にカプセル化できます。 プロパティとして公開するのではなく、HttpClient インスタンスを内部的に呼び出すパブリック メソッドを定義します。

public class RepoService
{
    // _httpClient isn't exposed publicly
    private readonly HttpClient _httpClient;

    public RepoService(HttpClient client)
    {
        _httpClient = client;
    }

    public async Task<IEnumerable<string>> GetRepos()
    {
        var response = await _httpClient.GetAsync("aspnet/repos");

        response.EnsureSuccessStatusCode();

        using var responseStream = await response.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync
            <IEnumerable<string>>(responseStream);
    }
}

上記のコードでは、HttpClient はプライベート フィールドに格納されます。 HttpClient へのアクセスは、パブリック GetRepos メソッドによって行われます。

生成されたクライアント

IHttpClientFactory は、Refit などのサードパーティ製ライブラリと組み合わせて使用できます。 Refit は、.NET 用の REST ライブラリです。 REST API をライブ インターフェイスに変換します。 インターフェイスの実装は RestService によって動的に生成され、HttpClient を使用して外部 HTTP の呼び出しを行います。

インターフェイスと応答は、外部の API とその応答を表すように定義されます。

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

型指定されたクライアントを追加し、Refit を使用して実装を生成できます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("http://localhost:5000");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddControllers();
}

定義されたインターフェイスは、DI および Refit によって提供される実装で必要に応じて使用できます。

[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IHelloClient _client;

    public ValuesController(IHelloClient client)
    {
        _client = client;
    }

    [HttpGet("/")]
    public async Task<ActionResult<Reply>> Index()
    {
        return await _client.GetMessageAsync();
    }
}

POST、PUT、DELETE の要求を行う

前の例では、すべての HTTP 要求で GET HTTP 動詞が使用されています。 HttpClient では、次のような他の HTTP 動詞もサポートされています。

  • POST
  • PUT
  • Del
  • PATCH

サポートされている HTTP 動詞の一覧については、「HttpMethod」を参照してください。

次の例は、HTTP POST 要求の方法を示しています。

public async Task CreateItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
        Encoding.UTF8,
        "application/json");

    using var httpResponse =
        await _httpClient.PostAsync("/api/TodoItems", todoItemJson);

    httpResponse.EnsureSuccessStatusCode();
}

上記のコードの CreateItemAsync メソッドは:

  • System.Text.Json を使用して TodoItem パラメーターを JSON にシリアル化します。 これは、JsonSerializerOptions のインスタンスを使用して、シリアル化プロセスを構成します。
  • HTTP 要求の本文で送信するためにシリアル化された JSON をパッケージ化する StringContent のインスタンスを作成します。
  • PostAsync を呼び出して、指定した URL に JSON の内容を送信します。 これは HttpClient.BaseAddress に追加される相対 URL です。
  • 応答状態コードが成功を示していない場合に、EnsureSuccessStatusCode を呼び出して例外をスローします。

HttpClient は、他の種類のコンテンツもサポートしています。 たとえば、MultipartContentStreamContent です。 サポートされているコンテンツの一覧については、「HttpContent」を参照してください。

HTTP PUT 要求の例を次に示します。

public async Task SaveItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem),
        Encoding.UTF8,
        "application/json");

    using var httpResponse =
        await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);

    httpResponse.EnsureSuccessStatusCode();
}

上記のコードは、POST の例によく似ています。 SaveItemAsync メソッドは PostAsync ではなく PutAsync を呼び出します。

HTTP DELETE 要求の例を次に示します。

public async Task DeleteItemAsync(long itemId)
{
    using var httpResponse =
        await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");

    httpResponse.EnsureSuccessStatusCode();
}

上記のコードの DeleteItemAsync メソッドは DeleteAsync を呼び出します。 HTTP DELETE 要求には通常、本文が含まれていないため、DeleteAsync メソッドは HttpContent のインスタンスを受け入れるオーバーロードを提供しません。

HttpClient でのさまざまな HTTP 動詞の使用の詳細については、「HttpClient」を参照してください。

送信要求ミドルウェア

HttpClient は、送信 HTTP 要求用にリンクできるハンドラーのデリゲートの概念を備えています。 IHttpClientFactory:

  • 各名前付きクライアントに適用するハンドラーの定義が簡単になります。

  • 送信要求ミドルウェア パイプラインを構築するための複数のハンドラーの登録とチェーン化がサポートされています。 これらの各ハンドラーでは、送信要求の前と後に処理を実行できます。 このパターンは次のようなものです。

    • ASP.NET Core での受信ミドルウェア パイプラインに似ています。
    • 次のような HTTP 要求に関する横断的な問題を管理するためのメカニズムが提供されます。
      • キャッシュ
      • エラー処理
      • シリアル化
      • ログ

デリゲート ハンドラーを作成するには、次のようにします。

  • DelegatingHandler から派生します。
  • SendAsync をオーバーライドします。 パイプライン内の次のハンドラーに要求を渡す前に、コードを実行します。
public class ValidateHeaderHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (!request.Headers.Contains("X-API-KEY"))
        {
            return new HttpResponseMessage(HttpStatusCode.BadRequest)
            {
                Content = new StringContent(
                    "You must supply an API key header called X-API-KEY")
            };
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

上記のコードでは、X-API-KEY ヘッダーが要求に含まれているかどうかが確認されます。 X-API-KEY がない場合は、BadRequest が返されます。

Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler を使用して HttpClient の構成に複数のハンドラーを追加できます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<ValidateHeaderHandler>();

    services.AddHttpClient("externalservice", c =>
    {
        // Assume this is an "external" service which requires an API KEY
        c.BaseAddress = new Uri("https://localhost:5001/");
    })
    .AddHttpMessageHandler<ValidateHeaderHandler>();

    // Remaining code deleted for brevity.

上記のコードでは、ValidateHeaderHandler が DI で登録されます。 登録が済むと、AddHttpMessageHandler を呼び出してハンドラーの型を渡すことができます。

実行する順序で、複数のハンドラーを登録することができます。 最後の HttpClientHandler が要求を実行するまで、各ハンドラーは次のハンドラーをラップします。

services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();

services.AddHttpClient("clientwithhandlers")
    // This handler is on the outside and called first during the 
    // request, last during the response.
    .AddHttpMessageHandler<SecureRequestHandler>()
    // This handler is on the inside, closest to the request being 
    // sent.
    .AddHttpMessageHandler<RequestDataHandler>();

送信要求ミドルウェアで DI を使用する

IHttpClientFactory によって新しいデリゲート ハンドラーが作成される際、ハンドラーのコンストラクター パラメーターを満たすために DI が使用されます。 IHttpClientFactory により、ハンドラーごとに 個別の DI スコープが作成されます。これにより、ハンドラーによって "スコープ付き" サービスが使用されるときに、予期しない動作が発生する可能性があります。

たとえば、識別子 OperationId を持つ操作としてタスクを表す、次のインターフェイスとその実装について考えてみます。

public interface IOperationScoped 
{
    string OperationId { get; }
}

public class OperationScoped : IOperationScoped
{
    public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

その名前が示すように、IOperationScoped は "スコープ付き" 有効期間を使用して DI に登録されます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<TodoContext>(options =>
        options.UseInMemoryDatabase("TodoItems"));

    services.AddHttpContextAccessor();

    services.AddHttpClient<TodoClient>((sp, httpClient) =>
    {
        var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;

        // For sample purposes, assume TodoClient is used in the context of an incoming request.
        httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
                                         httpRequest.Host, httpRequest.PathBase));
        httpClient.Timeout = TimeSpan.FromSeconds(5);
    });

    services.AddScoped<IOperationScoped, OperationScoped>();
    
    services.AddTransient<OperationHandler>();
    services.AddTransient<OperationResponseHandler>();

    services.AddHttpClient("Operation")
        .AddHttpMessageHandler<OperationHandler>()
        .AddHttpMessageHandler<OperationResponseHandler>()
        .SetHandlerLifetime(TimeSpan.FromSeconds(5));

    services.AddControllers();
    services.AddRazorPages();
}

次のデリゲート ハンドラーでは、IOperationScoped を使用して、送信要求用の X-OPERATION-ID ヘッダーを設定します。

public class OperationHandler : DelegatingHandler
{
    private readonly IOperationScoped _operationService;

    public OperationHandler(IOperationScoped operationScoped)
    {
        _operationService = operationScoped;
    }

    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);

        return await base.SendAsync(request, cancellationToken);
    }
}

HttpRequestsSample ダウンロード] で、/Operation に移動してページを更新します。 要求スコープの値は要求ごとに変更されますが、ハンドラーのスコープの値は 5 秒ごとに変更されるだけです。

ハンドラーは、任意のスコープのサービスに依存することができます。 ハンドラーが依存するサービスは、ハンドラーが破棄されるときに破棄されます。

次の方法のいずれかを使って、要求ごとの状態をメッセージ ハンドラーと共有します。

Polly ベースのハンドラーを使用する

IHttpClientFactory は、サードパーティのライブラリ Polly と統合します。 Polly は、.NET 用の包括的な回復力および一時的エラー処理ライブラリです。 開発者は、自然でスレッド セーフな方法を使用して、Retry、Circuit Breaker、Timeout、Bulkhead Isolation、Fallback などのポリシーを表現できます。

構成されている HttpClient インスタンスで Polly ポリシーを使用できるようにするための、拡張メソッドが提供されています。 Polly の拡張機能では、クライアントへの Polly ベースのハンドラーの追加がサポートされています。 Polly では、Microsoft.Extensions.Http.Polly NuGet パッケージが必要です。

一時的な障害を処理する

通常、外部 HTTP を呼び出すときに発生する障害は、一時的なものです。 AddTransientHttpErrorPolicy では、一時的なエラーを処理するためのポリシーを定義できます。 AddTransientHttpErrorPolicy で構成されたポリシーでは、次の応答が処理されます。

AddTransientHttpErrorPolicy では、可能性のある一時的障害を表すエラーを処理するために構成された PolicyBuilder オブジェクトへのアクセスが提供されます。

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient<UnreliableEndpointCallerService>()
        .AddTransientHttpErrorPolicy(p => 
            p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

    // Remaining code deleted for brevity.

上記のコードでは、WaitAndRetryAsync ポリシーが定義されています。 失敗した要求は最大 3 回再試行され、再試行の間には 600 ミリ秒の遅延が設けられます。

ポリシーを動的に選択する

Polly ベースのハンドラーを追加するための拡張メソッドが提供されています (AddPolicyHandler など)。 次の AddPolicyHandler のオーバーロードでは、要求が検査されて、適用するポリシーが決定されます。

var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
    .AddPolicyHandler(request => 
        request.Method == HttpMethod.Get ? timeout : longTimeout);

上記のコードでは、送信要求が HTTP GET の場合は、10 秒のタイムアウトが適用されます。 他の HTTP メソッドの場合は、30 秒のタイムアウトが使用されます。

複数の Polly ハンドラーを追加する

Polly のポリシーは入れ子にするのが一般的です。

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

前の例の場合:

  • 2 つのハンドラーが追加されます。
  • 1 つ目のハンドラーでは、AddTransientHttpErrorPolicy を使用して再試行ポリシーが追加されます。 失敗した要求は 3 回まで再試行されます。
  • 2 つ目の AddTransientHttpErrorPolicy の呼び出しでは、サーキット ブレーカー ポリシーが追加されます。 試行が連続して 5 回失敗した場合、それ以上の外部要求は 30 秒間ブロックされます。 サーキット ブレーカー ポリシーはステートフルです。 このクライアントからのすべての呼び出しは、同じサーキット状態を共有します。

Polly レジストリからポリシーを追加する

定期的に使用されるポリシーを管理するには、ポリシーを 1 回定義した後、PolicyRegistry でポリシーを登録します。

次のコードの内容は以下のとおりです。

  • "regular" ポリシーと "long" ポリシーが追加されます。
  • AddPolicyHandlerFromRegistry では、レジストリから "regular" ポリシーと "long" ポリシーが追加されます。
public void ConfigureServices(IServiceCollection services)
{           
    var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
        TimeSpan.FromSeconds(10));
    var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
        TimeSpan.FromSeconds(30));
    
    var registry = services.AddPolicyRegistry();

    registry.Add("regular", timeout);
    registry.Add("long", longTimeout);
    
    services.AddHttpClient("regularTimeoutHandler")
        .AddPolicyHandlerFromRegistry("regular");

    services.AddHttpClient("longTimeoutHandler")
       .AddPolicyHandlerFromRegistry("long");

    // Remaining code deleted for brevity.

IHttpClientFactory と Polly の統合の詳細については、Polly に関する wiki を参照してください。

HttpClient と有効期間の管理

IHttpClientFactoryCreateClient を呼び出すたびに、HttpClient の新しいインスタンスが返されます。 HttpMessageHandler は、名前付きクライアントごとに作成されます。 ファクトリによって HttpMessageHandler インスタンスの有効期間が管理されます。

IHttpClientFactory は、リソースの消費量を減らすため、ファクトリによって作成された HttpMessageHandler のインスタンスをプールします。 新しい HttpClient インスタンスを作成するときに、プールの HttpMessageHandler インスタンスの有効期間が切れていない場合はそれを再利用する場合があります。

通常、各ハンドラーでは基になる HTTP 接続が独自に管理されるため、ハンドラーはプールすることが望まれます。 必要以上のハンドラーを作成すると、接続に遅延が発生する可能性があります。 また、一部のハンドラーでは接続が無期限に開かれており、DNS (ドメイン ネーム システム) の変更にハンドラーが対応できないことがあります。

ハンドラーの既定の有効期間は 2 分です。 名前付きクライアントごとに、既定値をオーバーライドすることができます。

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient("extendedhandlerlifetime")
        .SetHandlerLifetime(TimeSpan.FromMinutes(5));

    // Remaining code deleted for brevity.

通常、HttpClient インスタンスは、破棄する必要の ない .NET オブジェクトとして扱うことができます。 破棄すると送信要求がキャンセルされ、Dispose の呼び出し後には指定された HttpClient インスタンスを使用できなくなります。 IHttpClientFactory は、HttpClient インスタンスによって使用されたリソースの追跡と破棄を行います。

IHttpClientFactory の登場以前は、1 つの HttpClient インスタンスを長い期間使い続けるパターンが一般的に使用されていました。 IHttpClientFactory に移行した後は、このパターンは不要になります。

IHttpClientFactory の代替手段

DI 対応のアプリ内で IHttpClientFactory を使用すれば、次のことを回避できます。

  • HttpMessageHandler インスタンスをプールすることによるリソース枯渇の問題。
  • 一定の間隔で HttpMessageHandler インスタンスを循環させることによって発生する古くなった DNS の問題。

有効期間の長い SocketsHttpHandler インスタンスを使用して、上記の問題を解決する別の方法があります。

  • アプリの起動時に SocketsHttpHandler インスタンスを作成し、アプリの有効期間中、それを使用します。
  • DNS の更新時間に基づいて、PooledConnectionLifetime を適切な値に構成します。
  • 必要に応じて new HttpClient(handler, disposeHandler: false) を使用して HttpClient インスタンスを作成します。

上記の方法を使用すると、IHttpClientFactory が同様の方法で解決するリソース管理の問題を解決できます。

  • SocketsHttpHandler を使用すると、HttpClient インスタンス間で接続を共有できます。 この共有によってソケットの枯渇が防止されます。
  • SocketsHttpHandler では、古くなった DNS の問題を回避するために PooledConnectionLifetime に従って接続を循環されます。

Cookies

HttpMessageHandler インスタンスをプールすると、CookieContainer オブジェクトが共有されます。 予期せぬ CookieContainer オブジェクト共有があると、多くの場合、コードは不適切なものとなります。 cookie を必要とするアプリの場合は、次のいずれかを検討してください。

  • 自動的な cookie 処理の無効化
  • IHttpClientFactory の回避

ConfigurePrimaryHttpMessageHandler を呼び出して、自動的な cookie 処理を無効にします。

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            UseCookies = false,
        };
    });

ログの記録

IHttpClientFactory によって作成されたクライアントは、すべての要求のログ メッセージを記録します。 既定のログ メッセージを見るには、ログの構成で適切な情報レベルを有効にします。 要求ヘッダーのログなどの追加ログは、トレース レベルでのみ含まれます。

各クライアントに使用されるログのカテゴリには、クライアントの名前が含まれます。 たとえば、MyNamedClient という名前のクライアントでは、"System.Net.Http.HttpClient.MyNamedClient.LogicalHandler" というカテゴリでメッセージがログに記録されます。 LogicalHandler というサフィックスが付いたメッセージが、要求ハンドラーのパイプラインの外部で発生します。 要求では、パイプライン内の他のハンドラーが要求を処理する前に、メッセージがログに記録されます。 応答では、他のパイプライン ハンドラーが応答を受け取った後で、メッセージがログに記録されます。

ログ記録は、要求ハンドラーのパイプラインの内部でも行われます。 MyNamedClient の例では、これらのメッセージはログ カテゴリ "System.Net.Http.HttpClient.MyNamedClient.ClientHandler" でログに記録されます。 要求では、他のすべてのハンドラーが実行された後、要求が送信される直前に、これが行われます。 応答では、このログ記録には、ハンドラー パイプラインを通じ戻される前の応答の状態が含まれます。

パイプラインの外部と内部でログを有効にすると、他のパイプライン ハンドラーによる変更を検査できます。 これには、要求ヘッダーや応答状態コードへの変更を含めることができます。

ログのカテゴリにクライアントの名前を含めると、特定の名前付きクライアントでログをフィルター処理できます。

HttpMessageHandler を構成する

クライアントによって使用される内部 HttpMessageHandler の構成を制御することが必要な場合があります。

名前付きクライアントまたは型指定されたクライアントを追加すると、IHttpClientBuilder が返されます。 ConfigurePrimaryHttpMessageHandler 拡張メソッドを使用して、デリゲートを定義することができます。 デリゲートは、そのクライアントによって使用されるプライマリ HttpMessageHandler の作成と構成に使用されます。

public void ConfigureServices(IServiceCollection services)
{            
    services.AddHttpClient("configured-inner-handler")
        .ConfigurePrimaryHttpMessageHandler(() =>
        {
            return new HttpClientHandler()
            {
                AllowAutoRedirect = false,
                UseDefaultCredentials = true
            };
        });

    // Remaining code deleted for brevity.

コンソール アプリで IHttpClientFactory を使用する

コンソール アプリで、次のパッケージ参照をプロジェクトに追加します。

次に例を示します。

  • IHttpClientFactory汎用ホストのサービス コンテナーに登録されます。
  • MyService により、クライアント ファクトリ インスタンスがサービスから作成され、それが HttpClient の作成に使用されます。 HttpClient は Web ページを取得する目的で使用されます。
  • Main により、サービスの GetPage メソッドを実行し、Web ページ コンテンツの最初の 500 文字をコンソールに書き込むためのスコープが作成されます。
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
    static async Task<int> Main(string[] args)
    {
        var builder = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHttpClient();
                services.AddTransient<IMyService, MyService>();
            }).UseConsoleLifetime();

        var host = builder.Build();

        try
        {
            var myService = host.Services.GetRequiredService<IMyService>();
            var pageContent = await myService.GetPage();

            Console.WriteLine(pageContent.Substring(0, 500));
        }
        catch (Exception ex)
        {
            var logger = host.Services.GetRequiredService<ILogger<Program>>();

            logger.LogError(ex, "An error occurred.");
        }

        return 0;
    }

    public interface IMyService
    {
        Task<string> GetPage();
    }

    public class MyService : IMyService
    {
        private readonly IHttpClientFactory _clientFactory;

        public MyService(IHttpClientFactory clientFactory)
        {
            _clientFactory = clientFactory;
        }

        public async Task<string> GetPage()
        {
            // Content from BBC One: Dr. Who website (©BBC)
            var request = new HttpRequestMessage(HttpMethod.Get,
                "https://www.bbc.co.uk/programmes/b006q2x0");
            var client = _clientFactory.CreateClient();
            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                return $"StatusCode: {response.StatusCode}";
            }
        }
    }
}

ヘッダー伝達ミドルウェア

ヘッダー伝達は、受信要求から送信 HTTP クライアント要求に HTTP ヘッダーを伝達するための ASP.NET Core ミドルウェアです。 ヘッダー伝達を使用するには、次を行います。

  • Microsoft.AspNetCore.HeaderPropagation パッケージを参照します。

  • Startup でミドルウェアと HttpClient を構成します。

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    
        services.AddHttpClient("MyForwardingClient").AddHeaderPropagation();
        services.AddHeaderPropagation(options =>
        {
            options.Headers.Add("X-TraceId");
        });
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseHttpsRedirection();
    
        app.UseHeaderPropagation();
    
        app.UseRouting();
    
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    
  • クライアントで、構成されたヘッダーが送信要求に含まれます。

    var client = clientFactory.CreateClient("MyForwardingClient");
    var response = client.GetAsync(...);
    

その他の技術情報