IHttpClientFactory 搭配 .NET

在本文中,您將了解如何使用各種 .NET 基礎概念來使用 IHttpClientFactory 以建立 HttpClient 類型,例如相依性插入 (DI)、記錄和設定。 此 HttpClient 型別是在 2012 年發行的 .NET Framework 4.5 中引進。 換句話說,它已經有一段時間了。 HttpClient 可用來提出 HTTP 要求,以及處理由 Uri 識別的 Web 資源之 HTTP 回應。 HTTP 通訊協定構成大部分的網際網路流量。

透過推動最佳做法的新式應用程式開發原則,IHttpClientFactory 作為處理站抽象概念,可建立具有自訂群組態的 HttpClient 執行個體。 IHttpClientFactory 已在 .NET Core 2.1 中引入。 常見的 HTTP 式 .NET 工作負載可以輕鬆利用復原和暫時性錯誤處理第三方中介軟體。

注意

如果您的應用程式需要 Cookie,則最好避免在應用程式中使用 IHttpClientFactory。 如需管理用戶端的替代方式,請參閱使用 HTTP 用戶端的指導方針

重要

IHttpClientFactory 所建立 HttpClient 執行個體的存留期管理,與手動建立的執行個體完全不同。 策略是使用 IHttpClientFactory 所建立的「短期」用戶端,或已設定 PooledConnectionLifetime 的「長期」用戶端。 如需詳細資訊,請參閱 HttpClient 存留期管理小節以及使用 HTTP 用戶端的指導方針

IHttpClientFactory 類型

本文中的所有樣本原始程式碼都需要使用 Microsoft.Extensions.Http NuGet 封裝。 此外,會向免費的 {JSON} 預留位置 API 提出 HTTP GET 要求,以取得使用者 Todo 物件。

當您呼叫任何 AddHttpClient 擴充方法時,會將 IHttpClientFactory 和相關的服務新增至 IServiceCollectionIHttpClientFactory 類型提供下列優點:

  • HttpClient 類別公開為 DI 就緒型別。
  • 提供一個集中位置以便命名和設定邏輯 HttpClient 執行個體。
  • 透過委派 HttpClient 中的處理常式來撰寫傳出中介軟體的概念。
  • 為基於 Polly 的中介軟體提供擴展方法,以利用 HttpClient 中的委派處理常式。
  • 管理基礎 HttpClientHandler 執行個體的快取和存留期。 自動管理避免了手動管理 HttpClient 存留期時會發生的網域名稱系統 (DNS) 問題。
  • 針對透過處理站所建立之用戶端傳送的所有要求,新增可設定的記錄體驗 (透過 ILogger)。

耗用模式

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

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

基本使用方式

若要登錄 IHttpClientFactory請呼叫 AddHttpClient

using Shared;
using BasicHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHttpClient();
builder.Services.AddTransient<TodoService>();

using IHost host = builder.Build();

使用服務可以要求 IHttpClientFactory 作為具有 DI 的建構函式參數。 下列程式碼會使用 IHttpClientFactory 來建立 HttpClient 執行個體:

using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;

namespace BasicHttp.Example;

public sealed class TodoService(
    IHttpClientFactory httpClientFactory,
    ILogger<TodoService> logger)
{
    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        // Create the client
        using HttpClient client = httpClientFactory.CreateClient();
        
        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo types
            Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
                $"https://jsonplaceholder.typicode.com/todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

            return todos ?? [];
        }
        catch (Exception ex)
        {
            logger.LogError("Error getting something fun to say: {Error}", ex);
        }

        return [];
    }
}

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

具名用戶端

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

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

具名 HttpClient 的設定可以在 IServiceCollection 上註冊期間指定:

using Shared;
using NamedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

string? httpClientName = builder.Configuration["TodoHttpClientName"];
ArgumentException.ThrowIfNullOrEmpty(httpClientName);

builder.Services.AddHttpClient(
    httpClientName,
    client =>
    {
        // Set the base address of the named client.
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");

        // Add a user-agent default request header.
        client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
    });

在上述程式碼中,會使用下列項目設定用戶端:

  • "TodoHttpClientName" 底下設定提取的名稱。
  • 基底位址 https://jsonplaceholder.typicode.com/
  • "User-Agent" 標題。

您可以使用設定來指定 HTTP 用戶端名稱,這在新增和建立時有助於避免將用戶端命名錯誤。 在此範例中,使用 appsettings.json 檔案來設定 HTTP 用戶端名稱:

{
    "TodoHttpClientName": "JsonPlaceholderApi"
}

可以輕易擴充此設定,並儲存更多有關 HTTP 用戶端運作方式的詳細資料。 如需詳細資訊,請參閱 .NET 中的設定

建立用戶端

每次呼叫 CreateClient 時:

  • 建立新的 HttpClient 執行個體。
  • 呼叫設定動作。

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

using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Shared;

namespace NamedHttp.Example;

public sealed class TodoService
{
    private readonly IHttpClientFactory _httpClientFactory = null!;
    private readonly IConfiguration _configuration = null!;
    private readonly ILogger<TodoService> _logger = null!;

    public TodoService(
        IHttpClientFactory httpClientFactory,
        IConfiguration configuration,
        ILogger<TodoService> logger) =>
        (_httpClientFactory, _configuration, _logger) =
            (httpClientFactory, configuration, logger);

    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        // Create the client
        string? httpClientName = _configuration["TodoHttpClientName"];
        using HttpClient client = _httpClientFactory.CreateClient(httpClientName ?? "");

        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo type
            Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
                $"todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

            return todos ?? [];
        }
        catch (Exception ex)
        {
            _logger.LogError("Error getting something fun to say: {Error}", ex);
        }

        return [];
    }
}

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

具型別用戶端

具型別用戶端:

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

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

using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;

namespace TypedHttp.Example;

public sealed class TodoService(
    HttpClient httpClient,
    ILogger<TodoService> logger) : IDisposable
{
    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo type
            Todo[]? todos = await httpClient.GetFromJsonAsync<Todo[]>(
                $"todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

            return todos ?? [];
        }
        catch (Exception ex)
        {
            logger.LogError("Error getting something fun to say: {Error}", ex);
        }

        return [];
    }

    public void Dispose() => httpClient?.Dispose();
}

在上述程式碼中:

  • 當具型別用戶端新增至服務集合時,就會進行設定。
  • 會將 HttpClient 指派為類別範圍的變數 (欄位),並搭配公開的 API 使用。

可以建立公開 HttpClient 功能的 API 特定方法。 例如,GetUserTodosAsync 方法會封裝程式碼,以擷取使用特定 Todo 物件。

下列程式碼會呼叫 AddHttpClient,以註冊具類型的用戶端類別:

using Shared;
using TypedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHttpClient<TodoService>(
    client =>
    {
        // Set the base address of the typed client.
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");

        // Add a user-agent default request header.
        client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
    });

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

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

重要

在 singleton 服務中使用具類型用戶端可能十分危險。 如需詳細資訊,請參閱避免在單一服務中使用具型別用戶端一節。

注意

使用 AddHttpClient<TClient> 方法註冊具類型的用戶端時,TClient 類型必須有會接受 HttpClient 參數的建構函式。 此外,TClient 類型不應該個別向 DI 容器註冊。

具名和具類型的用戶端

具名用戶端和具類型的用戶端有自己的優勢和弱點。 有一種方法可以結合這兩種用戶端類型,以取得兩邊的優點。

主要使用案例如下: 使用相同類型的用戶端,但針對不同的網域。 例如,您有主要和次要服務,而且它們會公開完全相同的功能。 這表示您可以使用相同類型用戶端來包裝 HttpClient 使用方式來發出要求、處理回應及處理錯誤。 將使用相同的程式碼,但會有不同的設定 (例如不同的基位地址、逾時和認證)。

下列範例會使用相同的 TodoService 型別用戶端,這會顯示在 型別用戶端 區段底下。

請先註冊具名和具類型的用戶端。

using Shared;
using TypedHttp.Example;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHttpClient<TodoService>("primary"
    client =>
    {
        // Configure the primary typed client
        client.BaseAddress = new Uri("https://primary-host-address.com/");
        client.Timeout = TimeSpan.FromSeconds(3);
    });

// Register the same typed client but with different settings
builder.Services.AddHttpClient<TodoService>("secondary"
    client =>
    {
        // Configure the secondary typed client
        client.BaseAddress = new Uri("https://secondary-host-address.com/");
        client.Timeout = TimeSpan.FromSeconds(10);
    });

在上述程式碼中:

  • 第一個 AddHttpClient 呼叫會在 primary 名稱下註冊 TodoService 型別的用戶端。 基礎 HttpClient 會指向主要服務,且逾時時間短暫。
  • 第二個 AddHttpClient 呼叫會在 secondary 名稱下註冊 TodoService 類型的用戶端。 基礎 HttpClient 會指向次要服務,且逾時時間較長。
using IHost host = builder.Build();

// Fetch an IHttpClientFactory instance to create a named client
IHttpClientFactory namedClientFactory =
    host.Services.GetRequiredService<IHttpClientFactory>();

// Fetch an ITypedHttpClientFactory<TodoService> instance to create a named and typed client
ITypedHttpClientFactory<TodoService> typedClientFactory  =
    host.Services.GetRequiredService<ITypedHttpClientFactory<TodoService>>();

// Create a TodoService instance against the primary host
var primaryClient = namedClientFactory.CreateClient("primary");
var todoService = typedClientFactory.CreateClient(primaryClient);

在上述程式碼中:

  • 從 DI 容器擷取 IHttpClientFactory 執行個體,以便透過其 CreateClient 方法建立具名用戶端。
  • 從 DI 容器擷取 ITypedHttpClientFactory<TodoService> 執行個體,以便透過其 CreateClient 方法建立具類型用戶端。
    • 這個 CreateClient 多載會收到一個具名 HttpClient (具有正確設定) 作為其參數。
    • 建立的 todoService 已設定為使用主要服務。

注意

IHttpClientFactory 類型位於 System.Net.Http 命名空間內,而 ITypedHttpClientFactory 類型則位於 Microsoft.Extensions.Http 內。

重要

使用實作類別 (在上述範例中的 TodoService) 作為 ITypedHttpClientFactory 的型別參數。 即使您也有抽象概念 (例如 ITodoService 介面),您仍然需要使用實作。 如果您不小心使用抽象概念 (ITodoService),則當您呼叫其 CreateClient 時,它會擲回 InvalidOperationException

try
{
    Todo[] todos = await todoService.GetUserTodosAsync(4);
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
    // The request timed out against the primary host

    // Create a TodoService instance against the secondary host
    var fallbackClient = namedClientFactory.CreateClient("secondary");
    var todoFallbackService = typedClientFactory.CreateClient(fallbackClient);

    // Issue request against the secondary host
    Todo[] todos = await todoFallbackService.GetUserTodosAsync(4);
}

在上述程式碼中:

  • 它會嘗試對主要服務發出要求。
  • 如果要求逾時 (花費超過 3 秒),則會擲回內部為 TimeoutExceptionTaskCanceledException
  • 如果逾時,就會建立與使用新的用戶端,並以次要服務為目標。

產生的用戶端

IHttpClientFactory 可和第三方程式庫一起使用,例如 Refit。 Refit 是適用於 .NET 的 REST 程式庫。 它允許宣告式 REST API 定義,將介面方法對應至端點。 介面的實作由 RestService 動態產生,並使用 HttpClient 進行外部 HTTP 呼叫。

請考慮下列 record 類型:

namespace Shared;

public record class Todo(
    int UserId,
    int Id,
    string Title,
    bool Completed);

下列範例依賴 Refit.HttpClientFactory NuGet 套件,而且是簡單的介面:

using Refit;
using Shared;

namespace GeneratedHttp.Example;

public interface ITodoService
{
    [Get("/todos?userId={userId}")]
    Task<Todo[]> GetUserTodosAsync(int userId);
}

上述 C# 介面:

  • 定義名為 GetUserTodosAsync 的方法,該方法會傳回 Task<Todo[]> 執行個體。
  • 使用路徑和查詢字串宣告 Refit.GetAttribute 屬性給外部 API。

可以新增具型別用戶端,使用 Refit 產生實作:

using GeneratedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Refit;
using Shared;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddRefitClient<ITodoService>()
    .ConfigureHttpClient(client =>
    {
        // Set the base address of the named client.
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");

        // Add a user-agent default request header.
        client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
    });

定義的介面可在需要時使用,並搭配 DI 與 Refit 所提供的實作。

提出 POST、PUT 和 DELETE 要求

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

  • POST
  • PUT
  • DELETE
  • PATCH

如需支援 HTTP 指令動詞的完整清單,請參閱 HttpMethod。 如需提出 HTTP 要求的詳細資訊,請參閱使用 HttpClient 傳送要求

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

public async Task CreateItemAsync(Item item)
{
    using StringContent json = new(
        JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
        Encoding.UTF8,
        MediaTypeNames.Application.Json);

    using HttpResponseMessage httpResponse =
        await httpClient.PostAsync("/api/items", json);

    httpResponse.EnsureSuccessStatusCode();
}

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

  • 使用 System.Text.JsonItem 參數序列化為 JSON。 這會使用 JsonSerializerOptions 的執行個體來設定序列化流程。
  • 建立 StringContent 的執行個體來封裝序列化的 JSON,以在 HTTP 要求本文中傳送。
  • 呼叫 PostAsync,將 JSON 內容傳送至指定的 URL。 這是新增至 HttpClient.BaseAddress 的相對 URL。
  • 如果回應狀態碼未表示成功,則呼叫 EnsureSuccessStatusCode 以擲回例外狀況。

HttpClient 也支援其他型別的內容。 例如,MultipartContentStreamContent。 如需完整的支援內容清單,請參閱 HttpContent

下列範例顯示 HTTP PUT 要求:

public async Task UpdateItemAsync(Item item)
{
    using StringContent json = new(
        JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
        Encoding.UTF8,
        MediaTypeNames.Application.Json);

    using HttpResponseMessage httpResponse =
        await httpClient.PutAsync($"/api/items/{item.Id}", json);

    httpResponse.EnsureSuccessStatusCode();
}

上述程式碼與 POST 範例非常類似。 UpdateItemAsync 方法會呼叫 PutAsync,而不是 PostAsync

下列範例顯示 HTTP DELETE 要求:

public async Task DeleteItemAsync(Guid id)
{
    using HttpResponseMessage httpResponse =
        await httpClient.DeleteAsync($"/api/items/{id}");

    httpResponse.EnsureSuccessStatusCode();
}

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

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

HttpClient 存留期管理

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

IHttpClientFactory 會快取處理站所建立的 HttpClientHandler 執行個體,以減少資源耗用量。 建立新的 HttpClient 執行個體時,如果其存留期間尚未過期,則可能會從快取中重複使用 HttpClientHandler 執行個體。

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

預設處理常式存留時間為兩分鐘。 若要覆寫預設值,請在 IServiceCollection 上呼叫每個用戶端的 SetHandlerLifetime

services.AddHttpClient("Named.Client")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

重要

IHttpClientFactory 所建立的 HttpClient 執行個體預計為「短期」

  • HttpMessageHandler 的存留期到期時將其回收和重新建立是 IHttpClientFactory 的必要動作,以確保處理常式回應 DNS 變更。 HttpClient 會在建立時繫結至特定處理常式執行個體,因此應該及時要求新的 HttpClient 執行個體,以確保用戶端將取得更新過的處理常式。

  • 處置「處理站所建立」的這類 HttpClient 執行個體,將不會導致通訊端耗盡,因為其處置「將不會」觸發 HttpMessageHandler 的處置。 IHttpClientFactory 會追蹤並處置用來建立 HttpClient 執行個體 (特別是 HttpMessageHandler 執行個體) 的資源,只要這些執行個體的存留期到期,而且 HttpClient 不再予以使用。

長時間讓單一 HttpClient 執行個體保持運作是一種常見模式,可用作 IHttpClientFactory 的「替代方案」,不過,此模式需要額外的設定,例如 PooledConnectionLifetime。 您可以搭配使用「長期」用戶端與 PooledConnectionLifetime,或 IHttpClientFactory 所建立的「短期」用戶端。 如需在應用程式中使用哪項策略的相關資訊,請參閱使用 HTTP 用戶端的指導方針

設定 HttpMessageHandler

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

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

.ConfigurePrimaryHttpMessageHandler(() =>
{
    return new HttpClientHandler
    {
        AllowAutoRedirect = false,
        UseDefaultCredentials = true
    };
});

設定 HttClientHandler 可讓您在處理常式的各種其他屬性中指定 HttpClient 執行個體的 Proxy。 如需詳細資訊,請參閱每個用戶端的 Proxy

其他設定

有數個額外的設定選項可用來控制 IHttpClientHandler

方法 描述
AddHttpMessageHandler 為具名 HttpClient 新增額外的訊息處理常式。
AddTypedClient 設定 TClient 和與 IHttpClientBuilder 建立關聯之具名 HttpClient 之間的繫結。
ConfigureHttpClient 新增將用於設定具名 HttpClient 的委派。
ConfigureHttpMessageHandlerBuilder 新增用於設定訊息處理常式的委派,其會針對具名 HttpClient 使用 HttpMessageHandlerBuilder
ConfigurePrimaryHttpMessageHandler 從具名 HttpClient 的相依性插入容器中設定主要 HttpMessageHandler
RedactLoggedHeaders 設定應在記錄前修訂其值的 HTTP 標頭名稱集合。
SetHandlerLifetime 設定可以重複使用 HttpMessageHandler 執行個體的時間長度。 每個具名用戶端都可以設定自己的處理常式存留期值。

搭配使用 IHttpClientFactory 與 SocketsHttpHandler

HttpMessageHandlerSocketsHttpHandler 實作已在 .NET Core 2.1 中新增,其允許設定 PooledConnectionLifetime。 此設定用來確保處理常式回應 DNS 變更,因此使用 SocketsHttpHandler 視為使用 IHttpClientFactory 的替代方案。 如需詳細資訊,請參閱使用 HTTP 用戶端的指導方針

不過,可以搭配使用 SocketsHttpHandlerIHttpClientFactory 來改善可設定性。 使用這兩個 API,即可受益於低階 (例如,使用 LocalCertificateSelectionCallback 進行動態憑證選取) 和高階 (例如,利用 DI 整合和數個用戶端設定) 的可設定性。

使用這兩個 API:

  1. SocketsHttpHandler 指定為 PrimaryHandler,並設定其 PooledConnectionLifetime (例如,設定為 HandlerLifetime 中的先前值)。
  2. 因為 SocketsHttpHandler 將處理連線共用和回收,所以不再需要 IHttpClientFactory 層級的處理常式回收。 您可以將 HandlerLifetime 設定為 Timeout.InfiniteTimeSpan,以將其停用。
services.AddHttpClient(name)
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new SocketsHttpHandler()
        {
            PooledConnectionLifetime = TimeSpan.FromMinutes(2)
        };
    })
    .SetHandlerLifetime(Timeout.InfiniteTimeSpan); // Disable rotation, as it is handled by PooledConnectionLifetime

在單一資料庫服務中避免具類型用戶端

使用「具名用戶端」方式時,會將 IHttpClientFactory 插入至服務,而且每次需要 HttpClient 時都會呼叫 CreateClient 來建立 HttpClient 執行個體。

不過,使用「具類型用戶端」方式時,具類型用戶端通常是插入至服務的暫時性物件。 這可能會造成問題,因為具類型用戶端可以插入至 singleton 服務。

重要

在與 IHttpClientFactory 所建立的 HttpClient 執行個體相同的情境中,具類型用戶端應該是「短期」(如需詳細資訊,請參閱HttpClient 存留期管理)。 只要建立具類型用戶端執行個體,IHttpClientFactory 就無法對其進行控制。 如果在 singleton 中擷取具類型用戶端執行個體,則可能會防止其回應 DNS 變更,並破壞 IHttpClientFactory 的其中一個用途。

如果您需要在 singleton 服務中使用 HttpClient 執行個體,則請考慮下列選項:

  • 請改用「具名用戶端」方式,並在 singleton 服務中插入 IHttpClientFactory,然後在必要時重新建立 HttpClient 執行個體。
  • 如果您需要「具類型用戶端」方式,則請使用將 PooledConnectionLifetime 設定為主要處理常式的 SocketsHttpHandler。 如需搭配使用 SocketsHttpHandlerIHttpClientFactory 的詳細資訊,請參閱搭配使用 IHttpClientFactory 與 SocketsHttpHandler 小節。

IHttpClientFactory 中的訊息處理常式範圍

IHttpClientFactory 會為每個 HttpMessageHandler 執行個體建立個別 DI 範圍。 這些 DI 範圍與應用程式 DI 範圍分開 (例如,ASP.NET 傳入要求範圍,或使用者建立的手動 DI 範圍),因此它們將不會共用限定範圍服務執行個體。 訊息處理常式範圍會繫結至處理常式存留期,而且可能會超過應用程式範圍,例如,這可能會在數個傳入要求之間重複使用具有相同已插入限定範圍相依性的相同 HttpMessageHandler 執行個體。

顯示兩個應用程式 DI 範圍和個別訊息處理常式範圍的圖表

強烈建議使用者「不要快取 HttpMessageHandler 執行個體內的範圍相關資訊」(例如來自 HttpContext 的資料),並謹慎使用限定範圍相依性,以避免洩漏敏感性資訊。

如果您需要從訊息處理常式存取應用程式 DI 範圍,以驗證為例,您會將範圍感知邏輯封裝在個別的暫時性 DelegatingHandler 中,並將它包裝在來自 IHttpClientFactory 快取的 HttpMessageHandler 執行個體周圍。 若要存取處理常式,請呼叫 IHttpMessageHandlerFactory.CreateHandler 以取得任何已註冊的具名用戶端。 在該情況下,您會使用建構的處理常式自行建立 HttpClient 執行個體。

顯示透過個別暫時性訊息處理常式和 IHttpMessageHandlerFactory 取得應用程式 DI 範圍存取的圖表

下列範例示範使用範圍感知 DelegatingHandler 建立 HttpClient

if (scopeAwareHandlerType != null)
{
    if (!typeof(DelegatingHandler).IsAssignableFrom(scopeAwareHandlerType))
    {
        throw new ArgumentException($"""
            Scope aware HttpHandler {scopeAwareHandlerType.Name} should
            be assignable to DelegatingHandler
            """);
    }

    // Create top-most delegating handler with scoped dependencies
    scopeAwareHandler = (DelegatingHandler)_scopeServiceProvider.GetRequiredService(scopeAwareHandlerType); // should be transient
    if (scopeAwareHandler.InnerHandler != null)
    {
        throw new ArgumentException($"""
            Inner handler of a delegating handler {scopeAwareHandlerType.Name} should be null.
            Scope aware HttpHandler should be registered as Transient.
            """);
    }
}

// Get or create HttpMessageHandler from HttpClientFactory
HttpMessageHandler handler = _httpMessageHandlerFactory.CreateHandler(name);

if (scopeAwareHandler != null)
{
    scopeAwareHandler.InnerHandler = handler;
    handler = scopeAwareHandler;
}

HttpClient client = new(handler);

進一步的因應措施可以是使用擴充方法來註冊範圍感知的 DelegatingHandler,並透過具有目前應用程式範圍存取權的暫時性服務覆寫預設的 IHttpClientFactory 註冊:

public static IHttpClientBuilder AddScopeAwareHttpHandler<THandler>(
    this IHttpClientBuilder builder) where THandler : DelegatingHandler
{
    builder.Services.TryAddTransient<THandler>();
    if (!builder.Services.Any(sd => sd.ImplementationType == typeof(ScopeAwareHttpClientFactory)))
    {
        // Override default IHttpClientFactory registration
        builder.Services.AddTransient<IHttpClientFactory, ScopeAwareHttpClientFactory>();
    }

    builder.Services.Configure<ScopeAwareHttpClientFactoryOptions>(
        builder.Name, options => options.HttpHandlerType = typeof(THandler));

    return builder;
}

如需詳細資訊,請參閱完整範例

另請參閱