在 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

IHttpClientFactory 可以註冊及用來在應用程式中設定和建立 HttpClient 執行個體。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 用戶端,來存取 GitHubFor 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 metapackage 的專案,已包含 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 方法內的 IServiceCollection 上呼叫 AddHttpClient 擴充方法來註冊。The IHttpClientFactory can be registered by calling the AddHttpClient extension method on the IServiceCollection, inside the Startup.ConfigureServices method.

services.AddHttpClient();

註冊之後,程式碼可以在可使用相依性插入 (DI) 插入服務的任何位置,接受 IHttpClientFactoryOnce 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 執行個體的位置,將那些項目取代為呼叫 CreateClientIn 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");
});

在上述程式碼中,會呼叫 AddHttpClient 並提供名稱 githubIn the preceding code, AddHttpClient is called, providing the name github. 此用戶端已套用一些預設組態—即使用 GitHub API 所需的基底位址和兩個標頭。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.

若要使用具名用戶端,可以傳遞字串參數至 CreateClientTo 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 並與其互動。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. 您可定義 API 特定的方法,其公開 HttpClient 功能。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 可和其他協力廠商程式庫一起使用,例如 RefitIHttpClientFactory 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.

在註冊期間,可以新增一或多個處理常式至 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 整合受歡迎的協力廠商程式庫,稱為 PollyIHttpClientFactory integrates with a popular third-party library called Polly. Polly 是適用於 .NET 的完整恢復功能和暫時性錯誤處理程式庫。Polly is a comprehensive resilience and transient fault-handling library for .NET. 它可讓開發人員以流暢且執行緒安全的方式表達原則,例如重試、斷路器、逾時、艙隔離與後援。It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

提供擴充方法來啟用使用 Polly 原則搭配設定的 HttpClient 執行個體。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. 失敗的要求會重試最多三次,並且在嘗試之間會有 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. 其中一個這類延伸模組是 AddPolicyHandler,它有多個多載。One such extension is AddPolicyHandler, which has multiple overloads. 一個多載可讓您在定義要套用的原則時,檢查要求: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)));

在上述範例中,會新增兩個處理常式。In the preceding example, two handlers are added. 第一個使用 AddTransientHttpErrorPolicy 延伸模組來新增重試原則。The first uses the AddTransientHttpErrorPolicy extension to add a retry policy. 失敗的要求會重試最多三次。Failed requests are retried up to three times. 第二個 AddTransientHttpErrorPolicy 呼叫會新增斷路器原則。The second call to AddTransientHttpErrorPolicy adds a circuit breaker policy. 如果循序發生五次失敗的嘗試,進一步的外部要求會遭到封鎖 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

管理定期使用原則的一個方法是定義一次,並向 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");

在上述程式碼中,當 PolicyRegistry 新增至 ServiceCollection 時,註冊了兩個原則。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

每次在 IHttpClientFactory 上呼叫 CreateClient 時,都會傳回新的 HttpClient 執行個體。A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. 每個具名用戶端都有一個 HttpMessageHandlerThere'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.

預設處理常式存留時間為兩分鐘。The default handler lifetime is two minutes. 可以針對每個具名用戶端覆寫預設值。The default value can be overridden on a per named client basis. 若要覆寫它,請在建立用戶端時所傳回的 IHttpClientBuilder 上呼叫 SetHandlerLifetimeTo 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. 處置會取消傳出的要求,並保證指定的 HttpClient 執行個體在呼叫 Dispose 之後無法使用。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 之前,讓單一 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.

設定 HttpMessageHandlerConfigure the HttpMessageHandler

可能需要控制用戶端使用之內部 HttpMessageHandler 的組態。It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

新增具名或具型別用戶端時,會傳回 IHttpClientBuilderAn IHttpClientBuilder is returned when adding named or typed clients. ConfigurePrimaryHttpMessageHandler 擴充方法可以用來定義委派。The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. 委派是用來建立及設定該用戶端所使用的主要 HttpMessageHandlerThe 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
        };
    });

在主控台應用程式中使用 IHttpClientFactoryUse 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 會從服務建立用戶端 Factory 執行個體,其可用來建立 HttpClientMyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient 會用來擷取網頁。HttpClient is used to retrieve a webpage.
  • Main 會建立範圍來執行服務的 GetPage 方法,並將網頁內容的前 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