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

寄稿者: Glenn CondronRyan NowakSteve GordonBy Glenn Condron, Ryan Nowak, and Steve Gordon

アプリ内で HttpClient インスタンスを構成して作成するために、IHttpClientFactory を登録して使用できます。An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. 次のような利点があります。It offers the following benefits:

  • 論理 HttpClient インスタンスの名前付けと構成を一元化します。Provides a central location for naming and configuring logical HttpClient instances. たとえば、github クライアントを登録して、GitHub にアクセスするように構成できます。For example, a github client can be registered and configured to access GitHub. 既定のクライアントは、他の目的に登録できます。A default client can be registered for other purposes.
  • HttpClient でのハンドラーのデリゲートにより送信ミドルウェアの概念を体系化し、Polly ベースのミドルウェアでそれを利用するための拡張機能を提供します。Codifies the concept of outgoing middleware via delegating handlers in HttpClient and provides extensions for Polly-based middleware to take advantage of that.
  • 基になっている HttpClientMessageHandler インスタンスのプールと有効期間を管理し、HttpClient の有効期間を手動で管理するときに発生する一般的な DNS の問題を防ぎます。Manages the pooling and lifetime of underlying HttpClientMessageHandler instances to avoid common DNS problems that occur when manually managing HttpClient lifetimes.
  • ファクトリによって作成されたクライアントから送信されるすべての要求に対し、(ILogger によって) 構成可能なログ エクスペリエンスを追加します。Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

サンプル コードを表示またはダウンロードします (ダウンロード方法)。View or download sample code (how to download)

必須コンポーネントPrerequisites

.NET Framework をターゲットとするプロジェクトでは、Microsoft.Extensions.Http NuGet パッケージのインストールが必要です。Projects targeting .NET Framework require installation of the Microsoft.Extensions.Http NuGet package. .NET Core をターゲットとし、Microsoft.AspNetCore.App メタパッケージを参照するプロジェクトには、既に Microsoft.Extensions.Http パッケージが含まれています。Projects that target .NET Core and reference the Microsoft.AspNetCore.App metapackage already include the Microsoft.Extensions.Http package.

利用パターンConsumption patterns

アプリで IHttpClientFactory を使用するには複数の方法があります。There are several ways IHttpClientFactory can be used in an app:

これらの間に厳密な優劣はありません。None of them are strictly superior to another. どの方法が最善かは、アプリの制約に依存します。The best approach depends upon the app's constraints.

基本的な使用方法Basic usage

IHttpClientFactory は、Startup.ConfigureServices メソッドの内部で IServiceCollectionAddHttpClient 拡張メソッドを呼び出すことによって登録できます。The IHttpClientFactory can be registered by calling the AddHttpClient extension method on the IServiceCollection, inside the Startup.ConfigureServices method.

services.AddHttpClient();

登録が済むと、コードは、依存関係の挿入 (DI) を使用してサービスを挿入できる任意の場所で、IHttpClientFactory を受け取ることができます。Once registered, code can accept an IHttpClientFactory anywhere services can be injected with dependency injection (DI). IHttpClientFactory を使用して、HttpClient インスタンスを作成できます。The IHttpClientFactory can be used to create a HttpClient instance:

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/aspnet/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)
        {
            Branches = await response.Content
                .ReadAsAsync<IEnumerable<GitHubBranch>>();
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }                               
    }
}

このように IHttpClientFactory を使用するのは、既存のアプリをリファクタリングする優れた方法です。Using IHttpClientFactory in this fashion is a good way to refactor an existing app. HttpClient の使用方法に影響はありません。It has no impact on the way HttpClient is used. 現在 HttpClient インスタンスが作成されている場所で、それを CreateClient の呼び出しに置き換えます。In places where HttpClient instances are currently created, replace those occurrences with a call to CreateClient.

名前付きクライアントNamed clients

それぞれ構成が異なる多数の HttpClient をアプリで個別に使用する必要がある場合は、名前付きクライアントを使用することができます。If an app requires many distinct uses of HttpClient, each with a different configuration, an option is to use named clients. 名前付き HttpClient の構成は、Startup.ConfigureServices での登録時に指定できます。Configuration for a named HttpClient can be specified during registration in 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");
});

上記のコードでは、github という名前を指定して AddHttpClient を呼び出しています。In the preceding code, AddHttpClient is called, providing the name github. このクライアントには既定の構成がいくつか適用されています。つまり、GitHub API を使用するために必要なベース アドレスと 2 つのヘッダーです。This client has some default configuration applied—namely the base address and two headers required to work with the GitHub API.

CreateClient を呼び出すたびに、HttpClient の新しいインスタンスが作成されて、構成アクションが呼び出されます。Each time CreateClient is called, a new instance of HttpClient is created and the configuration action is called.

名前付きクライアントを使用するには、文字列パラメーターを CreateClient に渡すことができます。To consume a named client, a string parameter can be passed to CreateClient. 作成するクライアントの名前を指定します。Specify the name of the client to be created:

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/aspnet/AspNetCore.Docs/pulls");

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

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            PullRequests = await response.Content
                .ReadAsAsync<IEnumerable<GitHubPullRequest>>();
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}

上記のコードでは、要求でホスト名を指定する必要はありません。In the preceding code, the request doesn't need to specify a hostname. クライアントに構成されているベース アドレスが使用されるため、パスだけを渡すことができます。It can pass just the path, since the base address configured for the client is used.

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

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

  • キーとして文字列を使用する必要なしに、名前付きクライアントと同じ機能を提供します。Provide the same capabilities as named clients without the need to use strings as keys.
  • クライアントを使用するときに、IntelliSense とコンパイラのヘルプが提供されます。Provides IntelliSense and compiler help when consuming clients.
  • 特定の HttpClient を構成してそれと対話する 1 つの場所を提供します。Provide a single location to configure and interact with a particular HttpClient. たとえば、単一の型指定されたクライアントを単一のバックエンド エンドポイントに対して使用し、そのエンドポイントを操作するすべてのロジックをカプセル化できます。For example, a single typed client might be used for a single backend endpoint and encapsulate all logic dealing with that endpoint.
  • DI に対応しており、アプリ内の必要な場所に挿入できます。Work with DI and can be injected where required in your app.

型指定されたクライアントは、コンストラクターで HttpClient パラメーターを受け取ります。A typed client accepts a HttpClient parameter in its constructor:

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/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

        var result = await response.Content
            .ReadAsAsync<IEnumerable<GitHubIssue>>();

        return result;
    }
}

上記のコードでは、構成が型指定されたクライアントに移動されています。In the preceding code, the configuration is moved into the typed client. HttpClient オブジェクトは、パブリック プロパティとして公開されます。The HttpClient object is exposed as a public property. HttpClient 機能を公開する API 固有のメソッドを定義することができます。It's possible to define API-specific methods that expose HttpClient functionality. GetAspNetDocsIssues メソッドは、GitHub リポジトリで最新の未解決の問題をクエリして解析するために必要なコードをカプセル化します。The GetAspNetDocsIssues method encapsulates the code needed to query for and parse out the latest open issues from a GitHub repository.

型指定されたクライアントを登録するには、ジェネリック AddHttpClient 拡張メソッドを Startup.ConfigureServices 内で使用して、型指定されたクライアントのクラスを指定します。To register a typed client, the generic AddHttpClient extension method can be used within Startup.ConfigureServices, specifying the typed client class:

services.AddHttpClient<GitHubService>();

型指定されたクライアントは、DI で一時的として登録されます。The typed client is registered as transient with DI. 型指定されたクライアントは、直接挿入して使用できます。The typed client can be injected and consumed directly:

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 での登録時に型指定されたクライアントの構成を指定できます。If preferred, the configuration for a typed client can be specified during registration in Startup.ConfigureServices, rather than in the typed client's constructor:

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 を完全にカプセル化することができます。It's possible to entirely encapsulate the HttpClient within a typed client. プロパティとして公開するのではなく、HttpClient インスタンスを内部的に呼び出すパブリック メソッドとして提供することができます。Rather than exposing it as a property, public methods can be provided which call the HttpClient instance internally.

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();

        var result = await response.Content
            .ReadAsAsync<IEnumerable<string>>();

        return result;
    }
}

上記のコードでは、HttpClient はプライベート フィールドとして格納されます。In the preceding code, the HttpClient is stored as a private field. 外部呼び出しを行うすべてのアクセスは、GetRepos メソッドを通して行われます。All access to make external calls goes through the GetRepos method.

生成されたクライアントGenerated clients

IHttpClientFactory は、Refit などの他のサードパーティ製ライブラリと組み合わせて使用できます。IHttpClientFactory can be used in combination with other third-party libraries such as Refit. Refit は、.NET 用の REST ライブラリです。Refit is a REST library for .NET. REST API をライブ インターフェイスに変換します。It converts REST APIs into live interfaces. インターフェイスの実装は RestService によって動的に生成され、HttpClient を使用して外部 HTTP の呼び出しを行います。An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

インターフェイスと応答は、外部の API とその応答を表すように定義されます。An interface and a reply are defined to represent the external API and its response:

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

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

型指定されたクライアントを追加し、Refit を使用して実装を生成できます。A typed client can be added, using Refit to generate the implementation:

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

    services.AddMvc();
}

定義されたインターフェイスは、DI および Refit によって提供される実装で必要に応じて使用できます。The defined interface can be consumed where necessary, with the implementation provided by DI and 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();
    }
}

送信要求ミドルウェアOutgoing request middleware

HttpClient は既に、送信 HTTP 要求用にリンクできるハンドラーのデリゲートの概念を備えています。HttpClient already has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactory を使用すると、各名前付きクライアントに適用するハンドラーを簡単に定義できます。The IHttpClientFactory makes it easy to define the handlers to apply for each named client. 複数のハンドラーを登録してチェーン化し、送信要求ミドルウェア パイプラインを構築できます。It supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. 各ハンドラーは、送信要求の前と後に処理を実行できます。Each of these handlers is able to perform work before and after the outgoing request. このパターンは、ASP.NET Core での受信ミドルウェア パイプラインに似ています。This pattern is similar to the inbound middleware pipeline in ASP.NET Core. このパターンは、キャッシュ、エラー処理、シリアル化、ログ記録など、HTTP 要求に関する横断的関心事を管理するためのメカニズムを提供します。The pattern provides a mechanism to manage cross-cutting concerns around HTTP requests, including caching, error handling, serialization, and logging.

ハンドラーを作成するには、DelegatingHandler の派生クラスを定義します。To create a handler, define a class deriving from DelegatingHandler. パイプライン内の次のハンドラーに要求を渡す前にコードを実行するように、SendAsync メソッドをオーバーライドします。Override the SendAsync method to execute code before passing the request to the next handler in the pipeline:

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);
    }
}

上記のコードでは、基本的なハンドラーが定義されています。The preceding code defines a basic handler. このコードは、X-API-KEY ヘッダーが要求に含まれていたかどうかを確認します。It checks to see if an X-API-KEY header has been included on the request. ヘッダーがない場合、HTTP 呼び出しを行わずに適切な応答を返すことができます。If the header is missing, it can avoid the HTTP call and return a suitable response.

登録の間に、1 つ以上のハンドラーを HttpClient の構成に追加することができます。During registration, one or more handlers can be added to the configuration for a HttpClient. このタスクは、IHttpClientBuilder の拡張メソッドを使用して実行されます。This task is accomplished via extension methods on the IHttpClientBuilder.

services.AddTransient<ValidateHeaderHandler>();

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

上記のコードでは、ValidateHeaderHandler が DI で登録されます。In the preceding code, the ValidateHeaderHandler is registered with DI. IHttpClientFactory では、ハンドラーごとに個別の DI スコープが作成されます。The IHttpClientFactory creates a separate DI scope for each handler. ハンドラーは、任意のスコープのサービスに自由に依存することができます。Handlers are free to depend upon services of any scope. ハンドラーが依存するサービスは、ハンドラーが破棄されるときに破棄されます。Services that handlers depend upon are disposed when the handler is disposed.

登録が済むと、AddHttpMessageHandler を呼び出してハンドラーの型を渡すことができます。Once registered, AddHttpMessageHandler can be called, passing in the type for the handler.

上記のコードでは、ValidateHeaderHandler が DI で登録されます。In the preceding code, the ValidateHeaderHandler is registered with DI. ハンドラーは、スコープではなく、一時的なサービスとして DI で登録する必要がありますThe handler must be registered in DI as a transient service, never scoped. ハンドラーがスコープ付きサービスとして登録されており、ハンドラーが依存するサービスを破棄できる場合:If the handler is registered as a scoped service and any services that the handler depends upon are disposable:

  • ハンドラーのサービスは、ハンドラーがスコープ外に出る前に破棄されることがあります。The handler's services could be disposed before the handler goes out of scope.
  • 破棄されたハンドラー サービスが原因でハンドラーが失敗します。The disposed handler services causes the handler to fail.

登録が済むと、AddHttpMessageHandler を呼び出してハンドラーの型を渡すことができます。Once registered, AddHttpMessageHandler can be called, passing in the handler type.

実行する順序で、複数のハンドラーを登録することができます。Multiple handlers can be registered in the order that they should execute. 最後の HttpClientHandler が要求を実行するまで、各ハンドラーは次のハンドラーをラップします。Each handler wraps the next handler until the final HttpClientHandler executes the request:

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>();

次の方法のいずれかを使って、要求ごとの状態をメッセージ ハンドラーと共有します。Use one of the following approaches to share per-request state with message handlers:

  • HttpRequestMessage.Properties を使ってデータをハンドラーに渡します。Pass data into the handler using HttpRequestMessage.Properties.
  • IHttpContextAccessor を使って現在の要求にアクセスします。Use IHttpContextAccessor to access the current request.
  • データを渡すカスタムの AsyncLocal ストレージ オブジェクトを作成します。Create a custom AsyncLocal storage object to pass the data.

Polly ベースのハンドラーを使用するUse Polly-based handlers

IHttpClientFactory は、Polly という名前の人気のあるサードパーティ製ライブラリと統合します。IHttpClientFactory integrates with a popular third-party library called Polly. Polly は、.NET 用の包括的な回復力および一時的エラー処理ライブラリです。Polly is a comprehensive resilience and transient fault-handling library for .NET. 開発者は、自然でスレッド セーフな方法を使用して、Retry、Circuit Breaker、Timeout、Bulkhead Isolation、Fallback などのポリシーを表現できます。It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

構成されている HttpClient インスタンスで Polly ポリシーを使用できるようにするための、拡張メソッドが提供されています。Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. Polly の拡張機能は、次のようになっています。The Polly extensions:

  • クライアントへの Polly ベースのハンドラーの追加がサポートされます。Support adding Polly-based handlers to clients.
  • Microsoft.Extensions.Http.Polly NuGet パッケージのインストール後に使用できます。Can be used after installing the Microsoft.Extensions.Http.Polly NuGet package. このパッケージは、ASP.NET Core 共有フレームワークに含まれていません。The package isn't included in the ASP.NET Core shared framework.

一時的な障害を処理するHandle transient faults

外部 HTTP を呼び出す際に発生する一般的な障害のほとんどは、一時的なものです。Most common faults occur when external HTTP calls are transient. AddTransientHttpErrorPolicy という便利な拡張メソッドが含まれており、一時的なエラーを処理するためのポリシーを定義できます。A convenient extension method called AddTransientHttpErrorPolicy is included which allows a policy to be defined to handle transient errors. この拡張メソッドで構成されたポリシーは、HttpRequestException、HTTP 5xx 応答、および HTTP 408 応答を処理します。Policies configured with this extension method handle HttpRequestException, HTTP 5xx responses, and HTTP 408 responses.

AddTransientHttpErrorPolicy 拡張メソッドは、Startup.ConfigureServices 内で使用できます。The AddTransientHttpErrorPolicy extension can be used within Startup.ConfigureServices. この拡張メソッドは、可能性のある一時的障害を表すエラーを処理するように構成された PolicyBuilder オブジェクトへのアクセスを提供します。The extension provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

services.AddHttpClient<UnreliableEndpointCallerService>()
    .AddTransientHttpErrorPolicy(p => 
        p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

上記のコードでは、WaitAndRetryAsync ポリシーが定義されています。In the preceding code, a WaitAndRetryAsync policy is defined. 失敗した要求は最大 3 回再試行され、再試行の間には 600 ミリ秒の遅延が設けられます。Failed requests are retried up to three times with a delay of 600 ms between attempts.

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

Polly ベースのハンドラーを追加するために使用できる追加の拡張メソッドが存在します。Additional extension methods exist which can be used to add Polly-based handlers. そのような拡張メソッドの 1 つは AddPolicyHandler であり、複数のオーバーロードを持ちます。One such extension is AddPolicyHandler, which has multiple overloads. 1 つのオーバーロードでは、適用するポリシーを定義するときに要求を検査できます。One overload allows the request to be inspected when defining which policy to apply:

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 秒のタイムアウトが適用されます。In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. 他の HTTP メソッドの場合は、30 秒のタイムアウトが使用されます。For any other HTTP method, a 30-second timeout is used.

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

Polly ポリシーを入れ子にして拡張機能を提供するのが一般的です。It's common to nest Polly policies to provide enhanced functionality:

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

前の例では、2 つのハンドラーが追加されています。In the preceding example, two handlers are added. 最初のハンドラーは、AddTransientHttpErrorPolicy 拡張メソッドを使用して再試行ポリシーを追加します。The first uses the AddTransientHttpErrorPolicy extension to add a retry policy. 失敗した要求は 3 回まで再試行されます。Failed requests are retried up to three times. AddTransientHttpErrorPolicy の 2 番目の呼び出しでは、サーキット ブレーカー ポリシーが追加されています。The second call to AddTransientHttpErrorPolicy adds a circuit breaker policy. 試行が連続して 5 回失敗した場合、それ以上の外部要求は 30 秒間ブロックされます。Further external requests are blocked for 30 seconds if five failed attempts occur sequentially. サーキット ブレーカー ポリシーはステートフルです。Circuit breaker policies are stateful. このクライアントからのすべての呼び出しは、同じサーキット状態を共有します。All calls through this client share the same circuit state.

Polly レジストリからポリシーを追加するAdd policies from the Polly registry

定期的に使用されるポリシーを管理するには、ポリシーを 1 回定義した後、PolicyRegistry でポリシーを登録します。An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry. 提供されている拡張メソッドを使用することで、レジストリからポリシーを使用してハンドラーを追加できます。An extension method is provided which allows a handler to be added using a policy from the registry:

var registry = services.AddPolicyRegistry();

registry.Add("regular", timeout);
registry.Add("long", longTimeout);

services.AddHttpClient("regulartimeouthandler")
    .AddPolicyHandlerFromRegistry("regular");

上記のコードでは、PolicyRegistryServiceCollection に追加されるときに 2 つのポリシーが登録されています。In the preceding code, two policies are registered when the PolicyRegistry is added to the ServiceCollection. レジストリからポリシーを使用するには、適用するポリシーの名前を渡して AddPolicyHandlerFromRegistry メソッドを使用します。To use a policy from the registry, the AddPolicyHandlerFromRegistry method is used, passing the name of the policy to apply.

IHttpClientFactory および Polly の統合について詳しくは、Polly の Wiki をご覧ください。Further information about IHttpClientFactory and Polly integrations can be found on the Polly wiki.

HttpClient と有効期間の管理HttpClient and lifetime management

IHttpClientFactoryCreateClient を呼び出すたびに、HttpClient の新しいインスタンスが返されます。A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. 名前付きクライアントごとに HttpMessageHandler が存在します。There's an HttpMessageHandler per named client. ファクトリによって HttpMessageHandler インスタンスの有効期間が管理されます。The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory は、リソースの消費量を減らすため、ファクトリによって作成された HttpMessageHandler のインスタンスをプールします。IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. 新しい HttpClient インスタンスを作成するときに、プールの HttpMessageHandler インスタンスの有効期間が切れていない場合はそれを再利用する場合があります。An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

通常、各ハンドラーでは基になる HTTP 接続が独自に管理されるため、ハンドラーはプールすることが望まれます。Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. 必要以上のハンドラーを作成すると、接続に遅延が発生する可能性があります。Creating more handlers than necessary can result in connection delays. また、一部のハンドラーは接続を無期限に開いており、DNS の変更にハンドラーが対応できないことがあります。Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

ハンドラーの既定の有効期間は 2 分です。The default handler lifetime is two minutes. 名前付きクライアントごとに、既定値をオーバーライドすることができます。The default value can be overridden on a per named client basis. オーバーライドするには、クライアント作成時に返された IHttpClientBuilderSetHandlerLifetime を呼び出します。To override it, call SetHandlerLifetime on the IHttpClientBuilder that is returned when creating the client:

services.AddHttpClient("extendedhandlerlifetime")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

クライアントを破棄する必要はありません。Disposal of the client isn't required. 破棄すると送信要求がキャンセルされ、Dispose の呼び出し後には指定された HttpClient インスタンスを使用できなくなります。Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory は、HttpClient インスタンスによって使用されたリソースの追跡と破棄を行います。IHttpClientFactory tracks and disposes resources used by HttpClient instances. 通常、HttpClient インスタンスは、破棄を必要としない .NET オブジェクトとして扱うことができます。The HttpClient instances can generally be treated as .NET objects not requiring disposal.

IHttpClientFactory の登場以前は、1 つの HttpClient インスタンスを長い期間使い続けるパターンが一般的に使用されていました。Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. IHttpClientFactory に移行した後は、このパターンは不要になります。This pattern becomes unnecessary after migrating to IHttpClientFactory.

ログの記録Logging

IHttpClientFactory によって作成されたクライアントは、すべての要求のログ メッセージを記録します。Clients created via IHttpClientFactory record log messages for all requests. 既定のログ メッセージを見るには、ログの構成で適切な情報レベルを有効にします。Enable the appropriate information level in your logging configuration to see the default log messages. 要求ヘッダーのログなどの追加ログは、トレース レベルでのみ含まれます。Additional logging, such as the logging of request headers, is only included at trace level.

各クライアントに使用されるログのカテゴリには、クライアントの名前が含まれます。The log category used for each client includes the name of the client. たとえば、MyNamedClient という名前のクライアントは、カテゴリ System.Net.Http.HttpClient.MyNamedClient.LogicalHandler でメッセージをログに記録します。A client named MyNamedClient, for example, logs messages with a category of System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. LogicalHandler というサフィックスが付いたメッセージが、要求ハンドラーのパイプラインの外部で発生します。Messages suffixed with LogicalHandler occur outside the request handler pipeline. 要求では、パイプライン内の他のハンドラーが要求を処理する前に、メッセージがログに記録されます。On the request, messages are logged before any other handlers in the pipeline have processed it. 応答では、他のパイプライン ハンドラーが応答を受け取った後で、メッセージがログに記録されます。On the response, messages are logged after any other pipeline handlers have received the response.

ログ記録は、要求ハンドラーのパイプラインの内部でも行われます。Logging also occurs inside the request handler pipeline. MyNamedClient の例では、それらのメッセージはログ カテゴリ System.Net.Http.HttpClient.MyNamedClient.ClientHandler に対して記録されます。In the MyNamedClient example, those messages are logged against the log category System.Net.Http.HttpClient.MyNamedClient.ClientHandler. 要求では、他のすべてのハンドラーが実行した後、要求がネットワークに送信される直前に、これが行われます。For the request, this occurs after all other handlers have run and immediately before the request is sent out on the network. 応答では、このログ記録には、ハンドラー パイプラインを通じ戻される前の応答の状態が含まれます。On the response, this logging includes the state of the response before it passes back through the handler pipeline.

パイプラインの外部と内部でログを有効にすると、他のパイプライン ハンドラーによる変更を検査できます。Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. これには、たとえば、要求ヘッダーや応答状態コードへの変更を含めることができます。This may include changes to request headers, for example, or to the response status code.

ログのカテゴリにクライアントの名前を含めると、必要に応じて、特定の名前付きクライアントでログをフィルター処理できます。Including the name of the client in the log category enables log filtering for specific named clients where necessary.

HttpMessageHandler を構成するConfigure the HttpMessageHandler

クライアントによって使用される内部 HttpMessageHandler の構成を制御することが必要な場合があります。It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

名前付きクライアントまたは型指定されたクライアントを追加すると、IHttpClientBuilder が返されます。An IHttpClientBuilder is returned when adding named or typed clients. ConfigurePrimaryHttpMessageHandler 拡張メソッドを使用して、デリゲートを定義することができます。The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. デリゲートは、そのクライアントによって使用されるプライマリ HttpMessageHandler の作成と構成に使用されます。The delegate is used to create and configure the primary HttpMessageHandler used by that client:

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

コンソール アプリで IHttpClientFactory を使用するUse IHttpClientFactory in a console app

コンソール アプリで、次のパッケージ参照をプロジェクトに追加します。In a console app, add the following package references to the project:

次に例を示します。In the following example:

  • IHttpClientFactory汎用ホストのサービス コンテナーに登録されます。IHttpClientFactory is registered in the Generic Host's service container.
  • MyService により、クライアント ファクトリ インスタンスがサービスから作成され、それが HttpClient の作成に使用されます。MyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient は Web ページを取得する目的で使用されます。HttpClient is used to retrieve a webpage.
  • Main により、サービスの GetPage メソッドを実行し、Web ページ コンテンツの最初の 500 文字をコンソールに書き込むためのスコープが作成されます。Main creates a scope to execute the service's GetPage method and write the first 500 characters of the webpage content to the console.
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();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

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

                Console.WriteLine(pageContent.Substring(0, 500));
            }
            catch (Exception ex)
            {
                var logger = 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}";
            }
        }
    }
}
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();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

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

                Console.WriteLine(pageContent.Substring(0, 500));
            }
            catch (Exception ex)
            {
                var logger = 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}";
            }
        }
    }
}

その他の技術情報Additional resources