在 ASP.NET Core 中使用 IHttpClientFactory 發出 HTTP 要求

作者: Kirk LarkinSteve Gordon一般 CondronRyan Nowak

IHttpClientFactory 可以註冊及用來在應用程式中設定和建立 HttpClient 執行個體。 IHttpClientFactory 提供下列優點:

  • 提供一個集中位置以便命名和設定邏輯 HttpClient 執行個體。 例如,名為 github 的用戶端可以註冊並設定為存取 GitHub。 預設用戶端可以註冊以進行一般存取。
  • 在 中 HttpClient 透過委派處理常式來合併傳出中介軟體的概念。 提供 Polly 型中介軟體的延伸模組,以利用 中的 HttpClient 委派處理常式。
  • 管理基礎 HttpClientMessageHandler 實例的共用和存留期。 自動管理可避免常見的 DNS (網域名稱系統) 手動管理 HttpClient 存留期時所發生的問題。
  • 針對透過處理站所建立之用戶端傳送的所有要求,新增可設定的記錄體驗 (透過 ILogger)。

本主題版本中的範例程式碼會使用 System.Text.Json 來還原序列化 JS HTTP 回應中傳回的 ON 內容。 對於使用 Json.NETReadAsAsync<T> 的範例,請使用版本選取器來選取本主題的 2.x 版本。

耗用量模式

有數種方式可將 IHttpClientFactory 用於應用程式:

最佳方法取決於應用程式的需求。

基本使用方式

在 中 Program.cs 呼叫 AddHttpClient 來註冊 IHttpClientFactory

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddHttpClient();

IHttpClientFactory可以使用相依性插入 (DI) 要求 。 下列程式碼會使用 IHttpClientFactory 來建立 HttpClient 實例:

public class BasicModel : PageModel
{
    private readonly IHttpClientFactory _httpClientFactory;

    public BasicModel(IHttpClientFactory httpClientFactory) =>
        _httpClientFactory = httpClientFactory;

    public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

    public async Task OnGet()
    {
        var httpRequestMessage = new HttpRequestMessage(
            HttpMethod.Get,
            "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
        {
            Headers =
            {
                { HeaderNames.Accept, "application/vnd.github.v3+json" },
                { HeaderNames.UserAgent, "HttpRequestsSample" }
            }
        };

        var httpClient = _httpClientFactory.CreateClient();
        var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);

        if (httpResponseMessage.IsSuccessStatusCode)
        {
            using var contentStream =
                await httpResponseMessage.Content.ReadAsStreamAsync();
            
            GitHubBranches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(contentStream);
        }
    }
}

IHttpClientFactory 上述範例中使用 like 是重構現有應用程式的好方法。 它不會影響使用方式 HttpClient 。 在現有應用程式中建立實例的位置 HttpClient ,以呼叫 CreateClient 取代這些出現專案。

具名用戶端

具名用戶端在下列情況下是不錯的選擇:

  • 應用程式需要許多不同的 用法 HttpClient
  • 許多 HttpClient 都有不同的組態。

在 註冊 Program.cs 期間指定具名 HttpClient 的組態:

builder.Services.AddHttpClient("GitHub", httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com/");

    // using Microsoft.Net.Http.Headers;
    // The GitHub API requires two headers.
    httpClient.DefaultRequestHeaders.Add(
        HeaderNames.Accept, "application/vnd.github.v3+json");
    httpClient.DefaultRequestHeaders.Add(
        HeaderNames.UserAgent, "HttpRequestsSample");
});

在上述程式碼中,用戶端會使用下列專案進行設定:

  • 基底位址 https://api.github.com/
  • 使用 GitHub API 所需的兩個標頭。

CreateClient

每次呼叫時 CreateClient

  • 建立 的新實例 HttpClient
  • 會呼叫組態動作。

若要建立具名用戶端,請將其名稱傳遞至 CreateClient

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _httpClientFactory;

    public NamedClientModel(IHttpClientFactory httpClientFactory) =>
        _httpClientFactory = httpClientFactory;

    public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

    public async Task OnGet()
    {
        var httpClient = _httpClientFactory.CreateClient("GitHub");
        var httpResponseMessage = await httpClient.GetAsync(
            "repos/dotnet/AspNetCore.Docs/branches");

        if (httpResponseMessage.IsSuccessStatusCode)
        {
            using var contentStream =
                await httpResponseMessage.Content.ReadAsStreamAsync();
            
            GitHubBranches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(contentStream);
        }
    }
}

在上述程式碼中,要求不需要指定主機名稱。 程式碼可以只傳遞路徑,因為會使用為用戶端設定的基底位址。

具型別用戶端

具型別用戶端:

  • 提供與具名用戶端相同的功能,而不需使用字串作為索引鍵。
  • 取用用戶端時提供 IntelliSense 和編譯器說明。
  • 提供單一位置來設定特定的 HttpClient 並與其互動。 例如,可能會使用單一型別用戶端:
    • 針對單一後端端點。
    • 封裝處理端點的所有邏輯。
  • 使用 DI,並可在應用程式中視需要插入。

具類型的用戶端在其建 HttpClient 構函式中接受參數:

public class GitHubService
{
    private readonly HttpClient _httpClient;

    public GitHubService(HttpClient httpClient)
    {
        _httpClient = httpClient;

        _httpClient.BaseAddress = new Uri("https://api.github.com/");

        // using Microsoft.Net.Http.Headers;
        // The GitHub API requires two headers.
        _httpClient.DefaultRequestHeaders.Add(
            HeaderNames.Accept, "application/vnd.github.v3+json");
        _httpClient.DefaultRequestHeaders.Add(
            HeaderNames.UserAgent, "HttpRequestsSample");
    }

    public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync() =>
        await _httpClient.GetFromJsonAsync<IEnumerable<GitHubBranch>>(
            "repos/dotnet/AspNetCore.Docs/branches");
}

在上述程式碼中:

  • 組態會移至具類型的用戶端。
  • 提供的 HttpClient 實例會儲存為私用欄位。

您可以建立 HttpClient 公開功能的 API 特定方法。 例如, GetAspNetCoreDocsBranches 方法會封裝程式碼以擷取檔 GitHub 分支。

下列程式碼會在 中 Program.cs 呼叫 AddHttpClient 以註冊 GitHubService 具類型的用戶端類別:

builder.Services.AddHttpClient<GitHubService>();

具型別用戶端會向 DI 註冊為暫時性。 在上述程式碼中, AddHttpClient 註冊 GitHubService 為暫時性服務。 此註冊會使用 Factory 方法來:

  1. 建立 HttpClient 執行個體。
  2. 建立 的 GitHubService 實例,並將 的實例 HttpClient 傳入其建構函式。

具型別用戶端可以直接插入並使用:

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;

    public TypedClientModel(GitHubService gitHubService) =>
        _gitHubService = gitHubService;

    public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

    public async Task OnGet()
    {
        try
        {
            GitHubBranches = await _gitHubService.GetAspNetCoreDocsBranchesAsync();
        }
        catch (HttpRequestException)
        {
            // ...
        }
    }
}

在 中 Program.cs 註冊時也可以指定具型別用戶端的組態,而不是在具型別用戶端的建構函式中指定:

builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com/");

    // ...
});

產生的用戶端

IHttpClientFactory 可以搭配協力廠商程式庫使用,例如 Refit。 Refit 是 REST 適用于 .NET 的程式庫。 它會將 REST API 轉換成即時介面。 呼叫 AddRefitClient 以產生介面的動態實作,這個介面會用來 HttpClient 進行外部 HTTP 呼叫。

自訂介面代表外部 API:

public interface IGitHubClient
{
    [Get("/repos/dotnet/AspNetCore.Docs/branches")]
    Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}

呼叫 AddRefitClient 以產生動態實作,然後呼叫 ConfigureHttpClient 以設定基礎 HttpClient

builder.Services.AddRefitClient<IGitHubClient>()
    .ConfigureHttpClient(httpClient =>
    {
        httpClient.BaseAddress = new Uri("https://api.github.com/");

        // using Microsoft.Net.Http.Headers;
        // The GitHub API requires two headers.
        httpClient.DefaultRequestHeaders.Add(
            HeaderNames.Accept, "application/vnd.github.v3+json");
        httpClient.DefaultRequestHeaders.Add(
            HeaderNames.UserAgent, "HttpRequestsSample");
    });

使用 DI 存取 的動態實作 IGitHubClient

public class RefitModel : PageModel
{
    private readonly IGitHubClient _gitHubClient;

    public RefitModel(IGitHubClient gitHubClient) =>
        _gitHubClient = gitHubClient;

    public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

    public async Task OnGet()
    {
        try
        {
            GitHubBranches = await _gitHubClient.GetAspNetCoreDocsBranchesAsync();
        }
        catch (ApiException)
        {
            // ...
        }
    }
}

提出 POST、PUT 和 DELETE 要求

在上述範例中,所有 HTTP 要求都會使用 GET HTTP 動詞命令。 HttpClient 也支援其他 HTTP 動詞,包括:

  • POST
  • PUT
  • DELETE
  • PATCH

如需支援 HTTP 動詞命令的完整清單,請參閱 HttpMethod

下列範例示範如何提出 HTTP POST 要求:

public async Task CreateItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem),
        Encoding.UTF8,
        Application.Json); // using static System.Net.Mime.MediaTypeNames;

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

    httpResponseMessage.EnsureSuccessStatusCode();
}

在上述程式碼中 CreateItemAsync ,方法:

  • 使用 System.Text.Json 將 參數序列化 TodoItem 為 JS ON。
  • 建立 的實例 StringContent ,以封裝序列化的 JS ON,以在 HTTP 要求的主體中傳送。
  • 呼叫 PostAsync 以將 JS ON 內容傳送至指定的 URL。 這是新增至 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 httpResponseMessage =
        await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);

    httpResponseMessage.EnsureSuccessStatusCode();
}

上述程式碼類似于 POST 範例。 方法會 SaveItemAsync 呼叫 PutAsyncPostAsync 而不是 。

下列範例顯示 HTTP DELETE 要求:

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

    httpResponseMessage.EnsureSuccessStatusCode();
}

在上述程式碼中,方法會 DeleteItemAsync 呼叫 DeleteAsync 。 因為 HTTP DELETE 要求通常不包含本文, DeleteAsync 所以 方法不會提供接受 實例的多 HttpContent 載。

若要深入瞭解搭配 使用不同的 HTTP 動詞命令 HttpClient ,請參閱 HttpClient

外寄要求中介軟體

HttpClient 具有委派處理常式的概念,這些處理常式可以連結在一起以供傳出 HTTP 要求使用。 IHttpClientFactory:

  • 簡化定義要針對每個具名用戶端套用的處理常式。
  • 支援多個處理常式的註冊和鏈結,以建置傳出要求中介軟體管線。 這些處理常式每個都可以在外寄要求之前和之後執行工作。 此模式:
    • 類似于 ASP.NET Core 中的輸入中介軟體管線。
    • 提供機制來管理 HTTP 要求的跨領域考慮,例如:
      • 快取
      • 錯誤處理
      • 序列化
      • logging

若要建立委派處理常式:

  • 衍生自 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(
                    "The API key header X-API-KEY is required.")
            };
        }

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

上述程式碼會 X-API-KEY 檢查標頭是否在要求中。 如果 X-API-KEY 遺漏 , BadRequest 則會傳回 。

您可以使用 將多個處理常式新增至 的 HttpClientMicrosoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler 組態:

builder.Services.AddTransient<ValidateHeaderHandler>();

builder.Services.AddHttpClient("HttpMessageHandler")
    .AddHttpMessageHandler<ValidateHeaderHandler>();

在上述程式碼,ValidateHeaderHandler 已向 DI 註冊。 註冊之後,便可以呼叫 AddHttpMessageHandler,並傳入處理常式的類型。

可以遵循應該執行的順序來註冊多個處理常式。 每個處理常式會包裝下一個處理常式,直到最終 HttpClientHandler 執行要求:

builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();

builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
    .AddHttpMessageHandler<SampleHandler1>()
    .AddHttpMessageHandler<SampleHandler2>();

在上述程式碼中, SampleHandler1 先執行 ,再執行 SampleHandler2

在傳出要求中介軟體中使用 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 註冊:

builder.Services.AddScoped<IOperationScoped, OperationScoped>();

下列委派處理常式會取用 IOperationScoped 並使用 來設定 X-OPERATION-ID 傳出要求的標頭:

public class OperationHandler : DelegatingHandler
{
    private readonly IOperationScoped _operationScoped;

    public OperationHandler(IOperationScoped operationScoped) =>
        _operationScoped = operationScoped;

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

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

在下載中HttpRequestsSample,流覽至 /Operation 並重新整理頁面。 要求範圍值會變更每個要求,但處理常式範圍值只會每 5 秒變更一次。

處理常式可以相依于任何範圍的服務。 處置處理常式時,會處置處理常式所相依的服務。

使用下列其中一種方式來與訊息處理常式共用個別要求狀態:

使用 Polly 為基礎的處理常式

IHttpClientFactory 與協力廠商程式庫 Polly整合。 Polly 是適用於 .NET 的完整恢復功能和暫時性錯誤處理程式庫。 它可讓開發人員以流暢且執行緒安全的方式表達原則,例如重試、斷路器、逾時、艙隔離與後援。

提供擴充方法來啟用使用 Polly 原則搭配設定的 HttpClient 執行個體。 Polly 延伸模組支援將 Polly 型處理常式新增至用戶端。 Polly 需要 Microsoft.Extensions.Http.Polly NuGet 套件。

處理暫時性錯誤

當外部 HTTP 呼叫是暫時性的時,通常會發生錯誤。 AddTransientHttpErrorPolicy 允許定義原則來處理暫時性錯誤。 設定的原則會 AddTransientHttpErrorPolicy 處理下列回應:

AddTransientHttpErrorPolicy 可讓您存取設定為 PolicyBuilder 處理代表可能暫時性錯誤的錯誤:

builder.Services.AddHttpClient("PollyWaitAndRetry")
    .AddTransientHttpErrorPolicy(policyBuilder =>
        policyBuilder.WaitAndRetryAsync(
            3, retryNumber => TimeSpan.FromMilliseconds(600)));

在上述程式碼,已定義了 WaitAndRetryAsync 原則。 失敗的要求會重試最多三次,並且在嘗試之間會有 600 毫秒的延遲時間。

動態選取原則

提供擴充方法以新增 Polly 型處理常式,例如 AddPolicyHandler 。 下列 AddPolicyHandler 多載會檢查要求以決定要套用的原則:

var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

builder.Services.AddHttpClient("PollyDynamic")
    .AddPolicyHandler(httpRequestMessage =>
        httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy : longTimeoutPolicy);

在上述程式碼中,如果外寄要求是 HTTP GET,就會套用 10 秒逾時。 任何其他 HTTP 方法會使用 30 秒逾時。

新增多個 Polly 處理常式

巢狀 Polly 原則很常見:

builder.Services.AddHttpClient("PollyMultiple")
    .AddTransientHttpErrorPolicy(policyBuilder =>
        policyBuilder.RetryAsync(3))
    .AddTransientHttpErrorPolicy(policyBuilder =>
        policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

在上述範例中:

  • 會新增兩個處理常式。
  • 第一個處理常式會使用 AddTransientHttpErrorPolicy 來新增重試原則。 失敗的要求會重試最多三次。
  • 第二 AddTransientHttpErrorPolicy 個呼叫會新增斷路器原則。 如果連續發生 5 次失敗的嘗試,則會封鎖進一步的外部要求 30 秒。 斷路器原則可設定狀態。 透過此用戶端的所有呼叫都會共用相同的線路狀態。

從 Polly 登錄新增原則

管理定期使用原則的一個方法是定義一次,並向 PolicyRegistry 註冊它們。 例如:

var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

var policyRegistry = builder.Services.AddPolicyRegistry();

policyRegistry.Add("Regular", timeoutPolicy);
policyRegistry.Add("Long", longTimeoutPolicy);

builder.Services.AddHttpClient("PollyRegistryRegular")
    .AddPolicyHandlerFromRegistry("Regular");

builder.Services.AddHttpClient("PollyRegistryLong")
    .AddPolicyHandlerFromRegistry("Long");

在上述程式碼中:

  • Long 兩個原則 Regular 會新增至 Polly 登錄。
  • AddPolicyHandlerFromRegistry 會設定個別具名用戶端,以從 Polly 登錄使用這些原則。

如需 和 Polly 整合的詳細資訊 IHttpClientFactory ,請參閱 Polly Wiki

HttpClient 和存留期管理

每次在 IHttpClientFactory 上呼叫 CreateClient 時,都會傳回新的 HttpClient 執行個體。 HttpMessageHandler會根據具名用戶端建立 。 處理站會管理 HttpMessageHandler 執行個體的存留期。

IHttpClientFactory 會將處理站所建立的 HttpMessageHandler 執行個體放入集區以減少資源耗用量。 建立新的 HttpClient 執行個體時,如果其存留期間尚未過期,HttpMessageHandler 執行個體可從集區重複使用。

將處理常式放入集區非常實用,因為處理常式通常會管理自己專屬的底層 HTTP 連線。 建立比所需數目更多的處理常式,可能會導致連線延遲。 有些處理常式也會無限期地讓連線保持開啟,這可防止處理常式回應 DNS (網域名稱系統) 變更。

預設處理常式存留時間為兩分鐘。 您可以根據每個具名用戶端覆寫預設值:

builder.Services.AddHttpClient("HandlerLifetime")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

HttpClient 實例通常可視為 不需要 處置的 .NET 物件。 處置會取消傳出的要求,並保證指定的 HttpClient 執行個體在呼叫 Dispose 之後無法使用。 IHttpClientFactory 會追蹤並處置 HttpClient 執行個體使用的資源。

在開始使用 IHttpClientFactory 之前,讓單一 HttpClient 執行個體維持一段較長的時間,是很常使用的模式。 在移轉到 IHttpClientFactory 之後,就不再需要此模式。

IHttpClientFactory 的替代方案

在啟用 DI 的應用程式中使用 IHttpClientFactory 可避免:

  • 藉由共用 HttpMessageHandler 實例的資源耗盡問題。
  • 定期迴圈 HttpMessageHandler 實例,以過期 DNS 問題。

使用長期 SocketsHttpHandler 實例來解決上述問題的替代方式。

  • 在應用程式啟動時建立 的 SocketsHttpHandler 實例,並將其用於應用程式生命週期。
  • PooledConnectionLifetime根據 DNS 重新整理時間設定為適當的值。
  • 視需要使用 建立 HttpClientnew HttpClient(handler, disposeHandler: false) 實例。

上述方法可解決以類似方式解決的資源管理問題 IHttpClientFactory

  • SocketsHttpHandlerHttpClient 實例共用連線。 此共用可防止通訊端耗盡。
  • SocketsHttpHandler 根據 PooledConnectionLifetime 來迴圈連線,以避免發生過時的 DNS 問題。

記錄

透過 IHttpClientFactory 建立的用戶端會記錄所有要求的記錄訊息。 在記錄組態中啟用適當的資訊層級,以查看預設記錄訊息。 額外的記錄功能,例如要求標頭的記錄,只會包含在追蹤層級。

用於每個用戶端的記錄檔分類包含用戶端的名稱。 例如,名為 MyNamedClient的用戶端會記錄類別為 「System.Net.Http.HttpClient」 的訊息。MyNamedClient。LogicalHandler」。 後面加上 LogicalHandler 的訊息發生在要求處理常式管線之外。 在要求中,訊息會在管線中任何其他處理常式處理它之前就記錄。 在回應中,訊息會在任何其他管線處理常式收到回應之後記錄。

記錄也會發生在要求處理常式管線之內。 在 MyNamedClient 範例中,這些訊息會記錄為記錄類別 「System.Net.Http.HttpClient。MyNamedClient。ClientHandler」。 針對要求,這會發生在所有其他處理常式已執行之後,並在要求傳送之前立即執行。 在回應中,此記錄會包含回應傳回通過處理常式管線之前的狀態。

在管線內外啟用記錄,可讓您檢查其他管線處理常式所做的變更。 這可能包括要求標頭或回應狀態碼的變更。

在記錄類別中包含用戶端的名稱,可針對特定具名用戶端啟用記錄篩選。

設定 HttpMessageHandler

可能需要控制用戶端使用之內部 HttpMessageHandler 的組態。

新增具名或具型別用戶端時,會傳回 IHttpClientBuilderConfigurePrimaryHttpMessageHandler 擴充方法可以用來定義委派。 委派是用來建立及設定該用戶端所使用的主要 HttpMessageHandler

builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
    .ConfigurePrimaryHttpMessageHandler(() =>
        new HttpClientHandler
        {
            AllowAutoRedirect = true,
            UseDefaultCredentials = true
        });

Cookie

集區 HttpMessageHandler 實例會導致 CookieContainer 共用物件。 未預期的 CookieContainer 物件共用通常會導致不正確的程式碼。 針對需要 cookie 的應用程式,請考慮:

  • 停用自動 cookie 處理
  • 避免 IHttpClientFactory

呼叫 ConfigurePrimaryHttpMessageHandler 以停用自動 cookie 處理:

builder.Services.AddHttpClient("NoAutomaticCookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
        new HttpClientHandler
        {
            UseCookies = false
        });

在主控台應用程式中使用 IHttpClientFactory

在主控台應用程式中,將下列套件參考新增至專案:

在下例中︰

  • IHttpClientFactoryGitHubService 會在 泛型主機 的服務容器中註冊。
  • GitHubService 會從 DI 要求,而該 DI 會接著要求 的 IHttpClientFactory 實例。
  • GitHubService 會使用 IHttpClientFactory 來建立 的 HttpClient 實例,它會用來擷取檔 GitHub 分支。
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var host = new HostBuilder()
    .ConfigureServices(services =>
    {
        services.AddHttpClient();
        services.AddTransient<GitHubService>();
    })
    .Build();

try
{
    var gitHubService = host.Services.GetRequiredService<GitHubService>();
    var gitHubBranches = await gitHubService.GetAspNetCoreDocsBranchesAsync();

    Console.WriteLine($"{gitHubBranches?.Count() ?? 0} GitHub Branches");

    if (gitHubBranches is not null)
    {
        foreach (var gitHubBranch in gitHubBranches)
        {
            Console.WriteLine($"- {gitHubBranch.Name}");
        }
    }
}
catch (Exception ex)
{
    host.Services.GetRequiredService<ILogger<Program>>()
        .LogError(ex, "Unable to load branches from GitHub.");
}

public class GitHubService
{
    private readonly IHttpClientFactory _httpClientFactory;

    public GitHubService(IHttpClientFactory httpClientFactory) =>
        _httpClientFactory = httpClientFactory;

    public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync()
    {
        var httpRequestMessage = new HttpRequestMessage(
            HttpMethod.Get,
            "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
        {
            Headers =
            {
                { "Accept", "application/vnd.github.v3+json" },
                { "User-Agent", "HttpRequestsConsoleSample" }
            }
        };

        var httpClient = _httpClientFactory.CreateClient();
        var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);

        httpResponseMessage.EnsureSuccessStatusCode();

        using var contentStream =
            await httpResponseMessage.Content.ReadAsStreamAsync();
        
        return await JsonSerializer.DeserializeAsync
            <IEnumerable<GitHubBranch>>(contentStream);
    }
}

public record GitHubBranch(
    [property: JsonPropertyName("name")] string Name);

標頭傳播中介軟體

標頭傳播是 ASP.NET Core中介軟體,可從傳入要求將 HTTP 標頭傳播至傳出 HttpClient 要求。 若要使用標頭傳播:

  • 安裝 Microsoft.AspNetCore.HeaderPropagation 套件。

  • 在 中 Program.cs 設定 HttpClient 和 中介軟體管線:

    // Add services to the container.
    builder.Services.AddControllers();
    
    builder.Services.AddHttpClient("PropagateHeaders")
        .AddHeaderPropagation();
    
    builder.Services.AddHeaderPropagation(options =>
    {
        options.Headers.Add("X-TraceId");
    });
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    app.UseHttpsRedirection();
    
    app.UseHeaderPropagation();
    
    app.MapControllers();
    
  • 使用已設定 HttpClient 的 實例發出輸出要求,其中包含新增的標頭。

其他資源

作者: Kirk LarkinSteve GordonKirn CondronRyan Nowak

IHttpClientFactory 可以註冊及用來在應用程式中設定和建立 HttpClient 執行個體。 IHttpClientFactory 提供下列優點:

  • 提供一個集中位置以便命名和設定邏輯 HttpClient 執行個體。 例如,名為 github 的用戶端可以註冊並設定為存取 GitHub。 預設用戶端可以註冊以進行一般存取。
  • 透過 委派 中的 HttpClient 處理常式,將傳出中介軟體的概念納入。 提供 Polly 型中介軟體的延伸模組,以利用 中的 HttpClient 委派處理常式。
  • 管理基礎 HttpClientMessageHandler 實例的共用和存留期。 自動管理可避免常見的 DNS (網域名稱系統) 手動管理 HttpClient 存留期時發生的問題。
  • 針對透過處理站所建立之用戶端傳送的所有要求,新增可設定的記錄體驗 (透過 ILogger)。

檢視或下載範例程式碼 (如何下載)。

本主題版本中的範例程式碼會使用 System.Text.Json 來還原序列化 JS HTTP 回應中傳回的 ON 內容。 對於使用 Json.NETReadAsAsync<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 上述範例中使用 like 是重構現有應用程式的好方法。 它不會影響使用方式 HttpClient 。 在現有應用程式中建立實例的位置 HttpClient ,以呼叫 CreateClient 取代這些發生專案。

具名用戶端

具名用戶端在下列情況下是不錯的選擇:

  • 應用程式需要許多不同的用法 HttpClient
  • 許多 HttpClient 都有不同的設定。

在 中註冊期間可以指定具名 HttpClientStartup.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 所需的兩個標頭。

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 並與其互動。 例如,可能會使用單一具類型的用戶端:
    • 針對單一後端端點。
    • 封裝處理端點的所有邏輯。
  • 使用 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 方法會封裝程式碼以擷取開啟的問題。

中的下列程式碼會呼叫 AddHttpClientStartup.ConfigureServices 以註冊具類型的用戶端類別:

services.AddHttpClient<GitHubService>();

具型別用戶端會向 DI 註冊為暫時性。 在上述程式碼中, AddHttpClient 註冊 GitHubService 為暫時性服務。 此註冊會使用 Factory 方法來:

  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 是 REST .NET 的程式庫。 它會將 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
  • DELETE
  • 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 為 JS ON。 這會使用 的 JsonSerializerOptions 實例來設定序列化程式。
  • 建立 的 StringContent 實例,以封裝序列化的 JS ON,以在 HTTP 要求的主體中傳送。
  • 呼叫 PostAsync 以將 JS ON 內容傳送至指定的 URL。 這是新增至 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 會呼叫 PutAsync ,而不是 PostAsync

下列範例顯示 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 要求的跨領域考慮,例如:
      • 快取
      • 錯誤處理
      • 序列化
      • logging

若要建立委派處理常式:

  • 衍生自 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 則會傳回 。

您可以使用 將多個處理常式新增至 的 HttpClientMicrosoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler 組態:

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 的完整恢復功能和暫時性錯誤處理程式庫。 它可讓開發人員以流暢且執行緒安全的方式表達原則,例如重試、斷路器、逾時、艙隔離與後援。

提供擴充方法來啟用使用 Polly 原則搭配設定的 HttpClient 執行個體。 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 原則。 失敗的要求會重試最多三次,並且在嘗試之間會有 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)));

在上述範例中:

  • 會新增兩個處理常式。
  • 第一個處理常式會使用 AddTransientHttpErrorPolicy 來新增重試原則。 失敗的要求會重試最多三次。
  • 第二 AddTransientHttpErrorPolicy 個呼叫會新增斷路器原則。 如果連續發生 5 次失敗的嘗試,則會封鎖進一步的外部要求 30 秒。 斷路器原則可設定狀態。 透過此用戶端的所有呼叫都會共用相同的線路狀態。

從 Polly 登錄新增原則

管理定期使用原則的一個方法是定義一次,並向 PolicyRegistry 註冊它們。

在下列程式碼中:

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.

如需 和 Polly 整合的詳細資訊 IHttpClientFactory ,請參閱 Polly Wiki

HttpClient 和存留期管理

每次在 IHttpClientFactory 上呼叫 CreateClient 時,都會傳回新的 HttpClient 執行個體。 HttpMessageHandler會根據具名用戶端建立 。 處理站會管理 HttpMessageHandler 執行個體的存留期。

IHttpClientFactory 會將處理站所建立的 HttpMessageHandler 執行個體放入集區以減少資源耗用量。 建立新的 HttpClient 執行個體時,如果其存留期間尚未過期,HttpMessageHandler 執行個體可從集區重複使用。

將處理常式放入集區非常實用,因為處理常式通常會管理自己專屬的底層 HTTP 連線。 建立比所需數目更多的處理常式,可能會導致連線延遲。 有些處理常式也會無限期地讓連線保持開啟,這可防止處理常式回應 DNS (網域名稱系統) 變更。

預設處理常式存留時間為兩分鐘。 您可以根據每個具名用戶端覆寫預設值:

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

    // Remaining code deleted for brevity.

HttpClient 實例通常可視為 不需要 處置的 .NET 物件。 處置會取消傳出的要求,並保證指定的 HttpClient 執行個體在呼叫 Dispose 之後無法使用。 IHttpClientFactory 會追蹤並處置 HttpClient 執行個體使用的資源。

在開始使用 IHttpClientFactory 之前,讓單一 HttpClient 執行個體維持一段較長的時間,是很常使用的模式。 在移轉到 IHttpClientFactory 之後,就不再需要此模式。

IHttpClientFactory 的替代方案

在啟用 DI 的應用程式中使用 IHttpClientFactory 可避免:

  • 藉由共用 HttpMessageHandler 實例的資源耗盡問題。
  • 定期迴圈 HttpMessageHandler 實例,以過期 DNS 問題。

使用長期 SocketsHttpHandler 實例來解決上述問題的替代方式。

  • 在應用程式啟動時建立 的 SocketsHttpHandler 實例,並將其用於應用程式生命週期。
  • PooledConnectionLifetime根據 DNS 重新整理時間設定為適當的值。
  • 視需要使用 建立 HttpClientnew HttpClient(handler, disposeHandler: false) 實例。

上述方法可解決以類似方式解決的資源管理問題 IHttpClientFactory

  • SocketsHttpHandlerHttpClient 實例共用連線。 此共用可防止通訊端耗盡。
  • SocketsHttpHandler 根據 PooledConnectionLifetime 來迴圈連線,以避免發生過時的 DNS 問題。

Cookie

集區 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 的組態。

新增具名或具型別用戶端時,會傳回 IHttpClientBuilderConfigurePrimaryHttpMessageHandler 擴充方法可以用來定義委派。 委派是用來建立及設定該用戶端所使用的主要 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 會從服務建立用戶端 Factory 執行個體,其可用來建立 HttpClientHttpClient 會用來擷取網頁。
  • Main 會建立範圍來執行服務的 GetPage 方法,並將網頁內容的前 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}";
            }
        }
    }
}

標頭傳播中介軟體

標頭傳播是 ASP.NET Core中介軟體,用來將 HTTP 標頭從傳入要求傳播至傳出 HTTP 用戶端要求。 若要使用標頭傳播:

  • 參考 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 GordonKirn CondronRyan Nowak

IHttpClientFactory 可以註冊及用來在應用程式中設定和建立 HttpClient 執行個體。 IHttpClientFactory 提供下列優點:

  • 提供一個集中位置以便命名和設定邏輯 HttpClient 執行個體。 例如,名為 github 的用戶端可以註冊並設定為存取 GitHub。 預設用戶端可以註冊以進行一般存取。
  • 透過 委派 中的 HttpClient 處理常式,將傳出中介軟體的概念納入。 提供 Polly 型中介軟體的延伸模組,以利用 中的 HttpClient 委派處理常式。
  • 管理基礎 HttpClientMessageHandler 實例的共用和存留期。 自動管理可避免常見的 DNS (網域名稱系統) 手動管理 HttpClient 存留期時發生的問題。
  • 針對透過處理站所建立之用戶端傳送的所有要求,新增可設定的記錄體驗 (透過 ILogger)。

檢視或下載範例程式碼 (如何下載)。

本主題版本中的範例程式碼會使用 System.Text.Json 來還原序列化 JS HTTP 回應中傳回的 ON 內容。 對於使用 Json.NETReadAsAsync<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 上述範例中使用 like 是重構現有應用程式的好方法。 它不會影響使用方式 HttpClient 。 在現有應用程式中建立實例的位置 HttpClient ,以呼叫 CreateClient 取代這些發生專案。

具名用戶端

具名用戶端在下列情況下是不錯的選擇:

  • 應用程式需要許多不同的用法 HttpClient
  • 許多 HttpClient 都有不同的設定。

在 中註冊期間可以指定具名 HttpClientStartup.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 所需的兩個標頭。

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 並與其互動。 例如,可能會使用單一具類型的用戶端:
    • 針對單一後端端點。
    • 封裝處理端點的所有邏輯。
  • 使用 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 方法會封裝程式碼以擷取開啟的問題。

中的下列程式碼會呼叫 AddHttpClientStartup.ConfigureServices 以註冊具類型的用戶端類別:

services.AddHttpClient<GitHubService>();

具型別用戶端會向 DI 註冊為暫時性。 在上述程式碼中, AddHttpClient 註冊 GitHubService 為暫時性服務。 此註冊會使用 Factory 方法來:

  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 是 REST .NET 的程式庫。 它會將 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
  • DELETE
  • 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 為 JS ON。 這會使用 的 JsonSerializerOptions 實例來設定序列化程式。
  • 建立 的 StringContent 實例,以封裝序列化的 JS ON,以在 HTTP 要求的主體中傳送。
  • 呼叫 PostAsync 以將 JS ON 內容傳送至指定的 URL。 這是新增至 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 會呼叫 PutAsync ,而不是 PostAsync

下列範例顯示 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 要求的跨領域考慮,例如:
      • 快取
      • 錯誤處理
      • 序列化
      • logging

若要建立委派處理常式:

  • 衍生自 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 則會傳回 。

您可以使用 將多個處理常式新增至 的 HttpClientMicrosoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler 組態:

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 的完整恢復功能和暫時性錯誤處理程式庫。 它可讓開發人員以流暢且執行緒安全的方式表達原則,例如重試、斷路器、逾時、艙隔離與後援。

提供擴充方法來啟用使用 Polly 原則搭配設定的 HttpClient 執行個體。 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 原則。 失敗的要求會重試最多三次,並且在嘗試之間會有 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 處理常式

巢狀輪詢原則很常見:

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

在上述範例中:

  • 會新增兩個處理常式。
  • 第一個處理常式會使用 AddTransientHttpErrorPolicy 來新增重試原則。 失敗的要求會重試最多三次。
  • 第二 AddTransientHttpErrorPolicy 個呼叫會新增斷路器原則。 如果連續發生 5 次失敗的嘗試,則會封鎖進一步的外部要求 30 秒。 斷路器原則可設定狀態。 透過此用戶端的所有呼叫都會共用相同的線路狀態。

從 Polly 登錄新增原則

管理定期使用原則的一個方法是定義一次,並向 PolicyRegistry 註冊它們。

在下列程式碼中:

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.

如需 和 Polly 整合的詳細資訊 IHttpClientFactory ,請參閱 Polly Wiki

HttpClient 和存留期管理

每次在 IHttpClientFactory 上呼叫 CreateClient 時,都會傳回新的 HttpClient 執行個體。 會針對每個具名用戶端建立 。 HttpMessageHandler 處理站會管理 HttpMessageHandler 執行個體的存留期。

IHttpClientFactory 會將處理站所建立的 HttpMessageHandler 執行個體放入集區以減少資源耗用量。 建立新的 HttpClient 執行個體時,如果其存留期間尚未過期,HttpMessageHandler 執行個體可從集區重複使用。

將處理常式放入集區非常實用,因為處理常式通常會管理自己專屬的底層 HTTP 連線。 建立比所需數目更多的處理常式,可能會導致連線延遲。 有些處理常式也會無限期地讓連線保持開啟狀態,以防止處理常式回應 DNS (網域名稱系統) 變更。

預設處理常式存留時間為兩分鐘。 您可以根據每個具名用戶端覆寫預設值:

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

    // Remaining code deleted for brevity.

HttpClient 實例通常可視為 不需要 處置的 .NET 物件。 處置會取消傳出的要求,並保證指定的 HttpClient 執行個體在呼叫 Dispose 之後無法使用。 IHttpClientFactory 會追蹤並處置 HttpClient 執行個體使用的資源。

在開始使用 IHttpClientFactory 之前,讓單一 HttpClient 執行個體維持一段較長的時間,是很常使用的模式。 在移轉到 IHttpClientFactory 之後,就不再需要此模式。

IHttpClientFactory 的替代方案

在已啟用 DI 的應用程式中使用 IHttpClientFactory 可避免:

  • 集區 HttpMessageHandler 實例的資源耗盡問題。
  • 定期迴圈實例來發生 HttpMessageHandler 過時的 DNS 問題。

有替代方式可以使用長期 SocketsHttpHandler 實例來解決上述問題。

  • 在應用程式啟動時建立 的實例 SocketsHttpHandler ,並將其用於應用程式生命週期。
  • PooledConnectionLifetime根據 DNS 重新整理時間設定為適當的值。
  • 視需要使用 建立 HttpClientnew HttpClient(handler, disposeHandler: false) 實例。

上述方法可解決以類似方式解決的資源管理問題 IHttpClientFactory

  • SocketsHttpHandlerHttpClient 實例共用連線。 此共用可防止通訊端耗盡。
  • SocketsHttpHandler 根據 PooledConnectionLifetime 來迴圈連線,以避免發生過時的 DNS 問題。

Cookie

集區 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 的組態。

新增具名或具型別用戶端時,會傳回 IHttpClientBuilderConfigurePrimaryHttpMessageHandler 擴充方法可以用來定義委派。 委派是用來建立及設定該用戶端所使用的主要 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 會從服務建立用戶端 Factory 執行個體,其可用來建立 HttpClientHttpClient 會用來擷取網頁。
  • Main 會建立範圍來執行服務的 GetPage 方法,並將網頁內容的前 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}";
            }
        }
    }
}

標頭傳播中介軟體

標頭傳播是 ASP.NET Core中介軟體,可從傳入要求將 HTTP 標頭傳播至傳出 HTTP 用戶端要求。 若要使用標頭傳播:

  • 參考 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(...);
    

其他資源

作者:Glenn CondronRyan NowakSteve Gordon

IHttpClientFactory 可以註冊及用來在應用程式中設定和建立 HttpClient 執行個體。 它提供下列優點:

  • 提供一個集中位置以便命名和設定邏輯 HttpClient 執行個體。 例如, github 用戶端可以註冊並設定為存取 GitHub。 預設用戶端可以註冊用於其他用途。
  • 透過委派 HttpClient 中的處理常式來撰寫外寄中介軟體的概念,並提供延伸模組以便 Polly 架構中介軟體利用外寄中介軟體。
  • 管理基礎 HttpClientMessageHandler 執行個體的共用和存留期,以避免在手動管理 HttpClient 存留期時,發生的常見 DNS 問題。
  • 針對透過處理站所建立之用戶端傳送的所有要求,新增可設定的記錄體驗 (透過 ILogger)。

檢視或下載範例程式碼 (如何下載)

先決條件

以 .NET Framework 為目標的專案,需要安裝 Microsoft.Extensions.Http NuGet 套件。 以 .NET Core 為目標且參考 Microsoft.AspNetCore.App metapackage 的專案,已包含 Microsoft.Extensions.Http 套件。

耗用量模式

有數種方式可將 IHttpClientFactory 用於應用程式:

它們全都不會嚴格優先於另一個。 最好的方法取決於應用程式的條件約束。

基本使用方式

IHttpClientFactory 可以藉由在 Startup.ConfigureServices 方法內的 IServiceCollection 上呼叫 AddHttpClient 擴充方法來註冊。

services.AddHttpClient();

註冊之後,程式碼就可以接受 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)
        {
            Branches = await response.Content
                .ReadAsAsync<IEnumerable<GitHubBranch>>();
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }                               
    }
}

以這種方式使用 IHttpClientFactory 是重構現有應用程式的好方法。 它對 HttpClient 的使用方式沒有任何影響。 在目前建立 HttpClient 執行個體的位置,將那些項目取代為呼叫 CreateClient

具名用戶端

如果應用程式需要使用多個不同的 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");
});

在上述程式碼中, AddHttpClient 會呼叫 ,並提供名稱 github。 此用戶端已套用一些預設組態,也就是使用 GitHub API 所需的基底位址和兩個標頭。

每次呼叫 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)
        {
            PullRequests = await response.Content
                .ReadAsAsync<IEnumerable<GitHubPullRequest>>();
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}

在上述程式碼中,要求不需要指定主機名稱。 它可以只傳遞路徑,因為已使用為用戶端設定的基底位址。

具型別用戶端

具型別用戶端:

  • 提供與具名用戶端相同的功能,而不需使用字串作為索引鍵。
  • 取用用戶端時提供 IntelliSense 和編譯器說明。
  • 提供單一位置來設定特定的 HttpClient 並與其互動。 例如,單一的具型別用戶端可能用於單一的後端端點,並封裝處理該端點的所有邏輯。
  • 使用 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();

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

        return result;
    }
}

在上述程式碼中,組態會移到具型別用戶端。 HttpClient 物件會公開為公用屬性。 您可定義 API 特定的方法,其公開 HttpClient 功能。 GetAspNetDocsIssues 方法會封裝從 GitHub 存放庫查詢和剖析最新開啟問題所需的程式碼。

若要註冊具型別用戶端,泛型 AddHttpClient 擴充方法可用於 Startup.ConfigureServices 內,並指定具型別用戶端類別:

services.AddHttpClient<GitHubService>();

具型別用戶端會向 DI 註冊為暫時性。 具型別用戶端可以直接插入並使用:

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

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

        return result;
    }
}

在上述程式碼,HttpClient 儲存為私用欄位。 進行外部呼叫的所有存取都會經歷 GetRepos 方法。

產生的用戶端

IHttpClientFactory 可和其他協力廠商程式庫一起使用,例如 Refit。 Refit 是 REST 適用于 .NET 的程式庫。 它會將 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.AddMvc();
}

定義的介面可在需要時使用,並搭配 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();
    }
}

外寄要求中介軟體

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 標頭。 如果遺漏標頭,它可以避免 HTTP 呼叫,並傳回適當的回應。

在註冊期間,可以將一或多個處理常式新增至 的 HttpClient 組態。 這項工作是透過 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 註冊。 處理常式必須在 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>();

使用下列其中一種方式來與訊息處理常式共用個別要求狀態:

  • 使用 HttpRequestMessage.Properties 將資料傳遞到處理常式。
  • 使用 IHttpContextAccessor 來存取目前的要求。
  • 建立自訂 AsyncLocal 儲存體物件以傳遞資料。

使用 Polly 為基礎的處理常式

IHttpClientFactory 整合受歡迎的協力廠商程式庫,稱為 Polly。 Polly 是適用於 .NET 的完整恢復功能和暫時性錯誤處理程式庫。 它可讓開發人員以流暢且執行緒安全的方式表達原則,例如重試、斷路器、逾時、艙隔離與後援。

提供擴充方法來啟用使用 Polly 原則搭配設定的 HttpClient 執行個體。 Polly 延伸模組:

  • 支援將以 Polly 為基礎的處理常式新增至用戶端。
  • 可在安裝 Microsoft.Extensions.Http.Polly NuGet 套件後使用。 該套件並未包含在 ASP.NET Core 共用架構中。

處理暫時性錯誤

大部分的錯誤發生在外部 HTTP 呼叫是暫時性的時候。 包含一個便利的擴充方法,稱為 AddTransientHttpErrorPolicy,它可允許定義原則來處理暫時性錯誤。 使用此延伸模組方法設定的原則,會處理 HttpRequestException、HTTP 5xx 回應和 HTTP 408 回應。

AddTransientHttpErrorPolicy 延伸模組可用於 Startup.ConfigureServices 內。 延伸模組能提供 PolicyBuilder 物件的存取,該物件已設定來處理代表可能暫時性錯誤的錯誤:

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

在上述程式碼,已定義了 WaitAndRetryAsync 原則。 失敗的要求會重試最多三次,並且在嘗試之間會有 600 毫秒的延遲時間。

動態選取原則

有額外的擴充方法可用來新增 Polly 為基礎的處理常式。 其中一個這類延伸模組是 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)));

在上述範例中,會新增兩個處理常式。 第一個使用 AddTransientHttpErrorPolicy 延伸模組來新增重試原則。 失敗的要求會重試最多三次。 第二個 AddTransientHttpErrorPolicy 呼叫會新增斷路器原則。 如果循序發生五次失敗的嘗試,進一步的外部要求會遭到封鎖 30 秒。 斷路器原則可設定狀態。 透過此用戶端的所有呼叫都會共用相同的線路狀態。

從 Polly 登錄新增原則

管理定期使用原則的一個方法是定義一次,並向 PolicyRegistry 註冊它們。 提供了擴充方法,可以使用來自登錄的原則新增處理常式:

var registry = services.AddPolicyRegistry();

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

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

在上述程式碼中,當 PolicyRegistry 新增至 ServiceCollection 時,註冊了兩個原則。 為了使用來自登錄的原則,使用了 AddPolicyHandlerFromRegistry 方法,並傳遞要套用的原則名稱。

關於 IHttpClientFactory Polly 整合的詳細資訊,可以在 Polly Wiki 上找到。

HttpClient 和存留期管理

每次在 IHttpClientFactory 上呼叫 CreateClient 時,都會傳回新的 HttpClient 執行個體。 每個具名用戶端都有一個 HttpMessageHandler。 處理站會管理 HttpMessageHandler 執行個體的存留期。

IHttpClientFactory 會將處理站所建立的 HttpMessageHandler 執行個體放入集區以減少資源耗用量。 建立新的 HttpClient 執行個體時,如果其存留期間尚未過期,HttpMessageHandler 執行個體可從集區重複使用。

將處理常式放入集區非常實用,因為處理常式通常會管理自己專屬的底層 HTTP 連線。 建立比所需數目更多的處理常式,可能會導致連線延遲。 有些處理常式也會保持連線無限期地開啟,這可能導致處理常式無法對 DNS 變更回應。

預設處理常式存留時間為兩分鐘。 可以針對每個具名用戶端覆寫預設值。 若要覆寫它,請在建立用戶端時所傳回的 IHttpClientBuilder 上呼叫 SetHandlerLifetime

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

不需要處置用戶端。 處置會取消傳出的要求,並保證指定的 HttpClient 執行個體在呼叫 Dispose 之後無法使用。 IHttpClientFactory 會追蹤並處置 HttpClient 執行個體使用的資源。 HttpClient 執行個體通常可視為 .NET 物件,不需要處置。

在開始使用 IHttpClientFactory 之前,讓單一 HttpClient 執行個體維持一段較長的時間,是很常使用的模式。 在移轉到 IHttpClientFactory 之後,就不再需要此模式。

IHttpClientFactory 的替代方案

在啟用 DI 的應用程式中使用 IHttpClientFactory 可避免:

  • 藉由共用 HttpMessageHandler 實例的資源耗盡問題。
  • 定期迴圈 HttpMessageHandler 實例,以過期 DNS 問題。

使用長期 SocketsHttpHandler 實例來解決上述問題的替代方式。

  • 在應用程式啟動時建立 的 SocketsHttpHandler 實例,並將其用於應用程式生命週期。
  • PooledConnectionLifetime根據 DNS 重新整理時間設定為適當的值。
  • 視需要使用 建立 HttpClientnew HttpClient(handler, disposeHandler: false) 實例。

上述方法可解決以類似方式解決的資源管理問題 IHttpClientFactory

  • SocketsHttpHandlerHttpClient 實例共用連線。 此共用可防止通訊端耗盡。
  • SocketsHttpHandler 根據 PooledConnectionLifetime 來迴圈連線,以避免發生過時的 DNS 問題。

Cookie

集區 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 的組態。

新增具名或具型別用戶端時,會傳回 IHttpClientBuilderConfigurePrimaryHttpMessageHandler 擴充方法可以用來定義委派。 委派是用來建立及設定該用戶端所使用的主要 HttpMessageHandler

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

在主控台應用程式中使用 IHttpClientFactory

在主控台應用程式中,將下列套件參考新增至專案:

在下例中︰

  • IHttpClientFactory 已在泛型主機的服務容器中註冊。
  • MyService 會從服務建立用戶端 Factory 執行個體,其可用來建立 HttpClientHttpClient 會用來擷取網頁。
  • 系統會執行服務的 GetPage 方法,將網頁內容的前 500 個字元寫入主控台。 如需從 Program.Main 呼叫服務的詳細資訊,請參閱ASP.NET Core中的相依性插入
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 用戶端要求。 若要使用標頭傳播:

  • 參考套件 HeaderPropagation的社群支援埠。 ASP.NET Core 3.1 和更新版本支援Microsoft.AspNetCore.HeaderPropagation

  • 在 中 Startup 設定中介軟體和 HttpClient

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    
        services.AddHttpClient("MyForwardingClient").AddHeaderPropagation();
        services.AddHeaderPropagation(options =>
        {
            options.Headers.Add("X-TraceId");
        });
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }
    
        app.UseHttpsRedirection();
    
        app.UseHeaderPropagation();
    
        app.UseMvc();
    }
    
  • 用戶端會在輸出要求中包含已設定的標頭:

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

其他資源