共用方式為


HttpWebRequest 至 HttpClient 移轉指南

本文旨在引導開發人員完成從 HttpWebRequestServicePoint,以及 ServicePointManager 移轉到 HttpClient 的程序。 由於舊版 API 已過時,而 HttpClient 又提供了許多優點,包括效能提升、資源管理更完善,以及 API 設計更現代化、更靈活,因此移轉是必要的。 透過遵循本文件所概述的步驟,開發人員將能順利轉換其代碼庫,並充分利用 HttpClient 所提供的功能。

警告

HttpWebRequestServicePointServicePointManager 移轉至 HttpClient 不只是「好」效能改善。 重要的是要了解,您移至 .NET (Core) 後,現有 WebRequest 邏輯的效能很可能會大幅降低。 這是因為 WebRequest 維持為最小相容性層,這意味著它缺乏許多最佳化,例如在許多情況下的連線重複使用。 因此,轉換到 HttpClient 對於確保您應用程式的效能和資源管理符合現代標準是非常重要的。

HttpWebRequest 移轉至 HttpClient

讓我們從撰寫一些範例開始:

使用 HttpWebRequest 進行簡單的 GET 要求

以下是程式碼外觀的範例:

HttpWebRequest request = WebRequest.CreateHttp(uri);
using WebResponse response = await request.GetResponseAsync();

使用 HttpClient 進行簡單的 GET 要求

以下是程式碼外觀的範例:

HttpClient client = new();
using HttpResponseMessage message = await client.GetAsync(uri);

使用 HttpWebRequest 進行簡單的 POST 要求

以下是程式碼外觀的範例:

HttpWebRequest request = WebRequest.CreateHttp(uri);
request.Method = "POST";
request.ContentType = "text/plain";
await using Stream stream = await request.GetRequestStreamAsync();
await stream.WriteAsync("Hello World!"u8.ToArray());
using WebResponse response = await request.GetResponseAsync();

使用 HttpClient 進行簡單的 POST 要求

以下是程式碼外觀的範例:

HttpClient client = new();
using HttpResponseMessage responseMessage = await client.PostAsync(uri, new StringContent("Hello World!"));

HttpWebRequest 至 HttpClient、SocketsHttpHandler 移轉指南

HttpWebRequest原始 API 新增 API 備註
Accept Accept 範例:設定請求標題
Address RequestUri 範例:擷取重新導向的 URI
AllowAutoRedirect AllowAutoRedirect 範例:設定 SocketsHttpHandler 屬性
AllowReadStreamBuffering 沒有直接的對等 API 緩衝屬性的使用方式
AllowWriteStreamBuffering 沒有直接的對等 API 緩衝屬性的使用方式
AuthenticationLevel 沒有直接的對等 API 範例:啟用相互驗證
AutomaticDecompression AutomaticDecompression 範例:設定 SocketsHttpHandler 屬性
CachePolicy 沒有直接的對等 API 範例:套用 CachePolicy 標題
ClientCertificates SslOptions.ClientCertificates HttpClient 中憑證相關屬性的使用方式
Connection Connection 範例:設定請求標題
ConnectionGroupName 沒有同等項目 API 沒有因應措施
ContentLength ContentLength 範例:設定內容標題
ContentType ContentType 範例:設定內容標題
ContinueDelegate 沒有同等項目 API 沒有因應措施。
ContinueTimeout Expect100ContinueTimeout 範例:設定 SocketsHttpHandler 屬性
CookieContainer CookieContainer 範例:設定 SocketsHttpHandler 屬性
Credentials Credentials 範例:設定 SocketsHttpHandler 屬性
Date Date 範例:設定請求標題
DefaultCachePolicy 沒有直接的對等 API 範例:套用 CachePolicy 標題
DefaultMaximumErrorResponseLength 沒有直接的對等 API 範例:在 HttpClient 中設定 MaximumErrorResponseLength
DefaultMaximumResponseHeadersLength 沒有同等項目 API 可以使用 MaxResponseHeadersLength 代替。
DefaultWebProxy 沒有同等項目 API 可以使用 Proxy 代替。
Expect Expect 範例:設定請求標題
HaveResponse 沒有同等項目 API 具有 HttpResponseMessage 執行個體。
Headers Headers 範例:設定請求標題
Host Host 範例:設定請求標題
IfModifiedSince IfModifiedSince 範例:設定請求標題
ImpersonationLevel 沒有直接的對等 API 範例:變更 ImpersonationLevel
KeepAlive 沒有直接的對等 API 範例:設定請求標題
MaximumAutomaticRedirections MaxAutomaticRedirections 範例:設定 SocketsHttpHandler 屬性
MaximumResponseHeadersLength MaxResponseHeadersLength 範例:設定 SocketsHttpHandler 屬性
MediaType 沒有直接的對等 API 範例:設定內容標題
Method Method 範例:HttpRequestMessage 屬性的使用方式
Pipelined 沒有同等項目 API HttpClient 不支援管線傳遞。
PreAuthenticate PreAuthenticate
ProtocolVersion HttpRequestMessage.Version 範例:HttpRequestMessage 屬性的使用方式
Proxy Proxy 範例:設定 SocketsHttpHandler 屬性
ReadWriteTimeout 沒有直接的對等 API SocketsHttpHandler 和 ConnectCallback 的使用方式
Referer Referrer 範例:設定請求標題
RequestUri RequestUri 範例:HttpRequestMessage 屬性的使用方式
SendChunked TransferEncodingChunked 範例:設定請求標題
ServerCertificateValidationCallback SslOptions.RemoteCertificateValidationCallback 範例:設定 SocketsHttpHandler 屬性
ServicePoint 沒有同等項目 API ServicePoint 不是 HttpClient 其中的一部分。
SupportsCookieContainer 沒有同等項目 API 這對 HttpClient 來說永遠是 true
Timeout Timeout
TransferEncoding TransferEncoding 範例:設定請求標題
UnsafeAuthenticatedConnectionSharing 沒有同等項目 API 沒有因應措施
UseDefaultCredentials 沒有直接的對等 API 範例:設定 SocketsHttpHandler 屬性
UserAgent UserAgent 範例:設定請求標題

移轉 ServicePoint(Manager) 使用方式

您應該注意,ServicePointManager 是靜態類別,這表示對其屬性所做的任何變更,都會對應用程式中所有新建立的 ServicePoint 物件產生全域影響。 例如,當您修改 ConnectionLimitExpect100Continue 等屬性時,將會影響每個新的 ServicePoint 範例。

警告

在現代的 .NET 中,HttpClient 不會考慮在 ServicePointManager 上設定的任何組態。

ServicePointManager屬性對應

ServicePointManager原始 API 新增 API 備註
CheckCertificateRevocationList SslOptions.CertificateRevocationCheckMode 範例:使用 SocketsHttpHandler 啟用 CRL 檢查
DefaultConnectionLimit MaxConnectionsPerServer 範例:設定 SocketsHttpHandler 屬性
DnsRefreshTimeout 沒有同等項目 API 範例:啟用 Dns 循環配置資源
EnableDnsRoundRobin 沒有同等項目 API 範例:啟用 Dns 循環配置資源
EncryptionPolicy SslOptions.EncryptionPolicy 範例:設定 SocketsHttpHandler 屬性
Expect100Continue ExpectContinue 範例:設定請求標題
MaxServicePointIdleTime PooledConnectionIdleTimeout 範例:設定 SocketsHttpHandler 屬性
MaxServicePoints 沒有同等項目 API ServicePoint 不是 HttpClient 其中的一部分。
ReusePort 沒有直接的對等 API SocketsHttpHandler 和 ConnectCallback 的使用方式
SecurityProtocol SslOptions.EnabledSslProtocols 範例:設定 SocketsHttpHandler 屬性
ServerCertificateValidationCallback SslOptions.RemoteCertificateValidationCallback 兩者都是 RemoteCertificateValidationCallback
UseNagleAlgorithm 沒有直接的對等 API SocketsHttpHandler 和 ConnectCallback 的使用方式

警告

在現代的 .NET 中,UseNagleAlgorithmExpect100Continue 屬性的預設值設定為 false。 這些值在 .NET Framework 中預設為 true

ServicePointManager 方法對應

ServicePointManager原始 API 新增 API 備註
FindServicePoint 沒有同等項目 API 沒有因應措施
SetTcpKeepAlive 沒有直接的對等 API SocketsHttpHandler 和 ConnectCallback 的使用方式

ServicePoint屬性對應

ServicePoint原始 API 新增 API 備註
Address HttpRequestMessage.RequestUri 這是要求 URI,您可以在 HttpRequestMessage 下找到這項資訊。
BindIPEndPointDelegate 沒有直接的對等 API SocketsHttpHandler 和 ConnectCallback 的使用方式
Certificate 沒有直接的對等 API 此資訊可以從 RemoteCertificateValidationCallback 擷取。 範例:擷取憑證
ClientCertificate 沒有同等項目 API 範例:啟用相互驗證
ConnectionLeaseTimeout SocketsHttpHandler.PooledConnectionLifetime HttpClient 中的對等設定
ConnectionLimit MaxConnectionsPerServer 範例:設定 SocketsHttpHandler 屬性
ConnectionName 沒有同等項目 API 沒有因應措施
CurrentConnections 沒有同等項目 API 請參閱 .NET 中的網路遙測
Expect100Continue ExpectContinue 範例:設定請求標題
IdleSince 沒有同等項目 API 沒有因應措施
MaxIdleTime PooledConnectionIdleTimeout 範例:設定 SocketsHttpHandler 屬性
ProtocolVersion HttpRequestMessage.Version 範例:HttpRequestMessage 屬性的使用方式
ReceiveBufferSize 沒有直接的對等 API SocketsHttpHandler 和 ConnectCallback 的使用方式
SupportsPipelining 沒有同等項目 API HttpClient 不支援管線傳遞。
UseNagleAlgorithm 沒有直接的對等 API SocketsHttpHandler 和 ConnectCallback 的使用方式

ServicePoint 方法對應

ServicePoint原始 API 新增 API 備註
CloseConnectionGroup 沒有對等項目 沒有因應措施
SetTcpKeepAlive 沒有直接的對等 API SocketsHttpHandler 和 ConnectCallback 的使用方式

HttpClient 和 HttpRequestMessage 屬性的使用方式

在 .NET 中使用 HttpClient 時,您可以存取各種屬性,這些屬性可讓您設定和自訂 HTTP 要求與回應。 了解這些屬性可以幫助您充分利用 HttpClient,並確保您的應用程式與 Web 服務進行有效且安全的通訊。

範例:HttpRequestMessage 屬性的使用方式

下面是一個如何一起使用 HttpClient 和 HttpRequestMessage 的範例:

var client = new HttpClient();

var request = new HttpRequestMessage(HttpMethod.Post, "https://example.com"); // Method and RequestUri usage
var request = new HttpRequestMessage() // Alternative way to set RequestUri and Method
{
    RequestUri = new Uri("https://example.com"),
    Method = HttpMethod.Post
};
request.Headers.Add("Custom-Header", "value");
request.Content = new StringContent("somestring");

using var response = await client.SendAsync(request);
var protocolVersion = response.RequestMessage.Version; // Fetch `ProtocolVersion`.

範例:擷取重新導向的 URI

以下是如何取得重新導向 URI 的範例 (與 HttpWebRequest.Address 相同):

var client = new HttpClient();
using var response = await client.GetAsync(uri);
var redirectedUri = response.RequestMessage.RequestUri;

SocketsHttpHandler 和 ConnectCallback 的使用方式

SocketsHttpHandler 中的 ConnectCallback 屬性允許開發人員自訂建立 TCP 連線的程序。 這對於需要控制 DNS 解析或在連線上套用特定通訊端選項的情況非常有用。 透過使用 ConnectCallback,您可以在 HttpClient 使用之前截取並修改連線程序。

範例:將 IP 位址繫結至通訊端

在使用 HttpWebRequest 的舊方法中,您可能會使用自訂邏輯將特定 IP 位址綁定到通訊端。 以下是使用 HttpClientConnectCallback 實現類似功能的方法:

使用 HttpWebRequest 的舊程式碼:

HttpWebRequest request = WebRequest.CreateHttp(uri);
request.ServicePoint.BindIPEndPointDelegate = (servicePoint, remoteEndPoint, retryCount) =>
{
    // Bind to a specific IP address
    IPAddress localAddress = IPAddress.Parse("192.168.1.100");
    return new IPEndPoint(localAddress, 0);
};
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

使用 HttpClientConnectCallback 的新程式碼:

var handler = new SocketsHttpHandler
{
    ConnectCallback = async (context, cancellationToken) =>
    {
        // Bind to a specific IP address
        IPAddress localAddress = IPAddress.Parse("192.168.1.100");
        var socket = new Socket(localAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        try
        {
            socket.Bind(new IPEndPoint(localAddress, 0));
            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken);
            return new NetworkStream(socket, ownsSocket: true);
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

範例:套用特定的通訊端選項

如果您需要應用特定的通訊端選項,例如啟用 TCP 保持運作,您可以在 HttpClient 使用套接字之前使用 ConnectCallback 來設定通訊端。 事實上,ConnectCallback 設定通訊端選項更有彈性。

使用 HttpWebRequest 的舊程式碼:

ServicePointManager.ReusePort = true;
HttpWebRequest request = WebRequest.CreateHttp(uri);
request.ServicePoint.SetTcpKeepAlive(true, 60000, 1000);
request.ServicePoint.ReceiveBufferSize = 8192;
request.ServicePoint.UseNagleAlgorithm = false;
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

使用 HttpClientConnectCallback 的新程式碼:

var handler = new SocketsHttpHandler
{
    ConnectCallback = async (context, cancellationToken) =>
    {
        var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
        try
        {
            // Setting TCP Keep Alive
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
            socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 60);
            socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 1);

            // Setting ReceiveBufferSize
            socket.ReceiveBufferSize = 8192;

            // Enabling ReusePort
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, true);

            // Disabling Nagle Algorithm
            socket.NoDelay = true;

            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken);
            return new NetworkStream(socket, ownsSocket: true);
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

範例:啟用 DNS 循環配置資源

DNS 循環配置資源是一種技術,用來透過與單一網域名相關聯的 IP 位址清單,將網路流量分散到多部伺服器。 這有助於進行負載平衡,並改善服務的可用性。 使用 HttpClient 時,您可以使用 SocketsHttpHandler 的 ConnectCallback 屬性,手動處理 DNS 解析並輪替 IP 位址,以實作 DNS 循環配置資源。

若要使用 HttpClient 啟用 DNS 循環配置資源,您可以使用 ConnectCallback 屬性手動解析 DNS 專案,並輪替 IP 位址。 以下是 HttpWebRequestHttpClient 的範例:

使用 HttpWebRequest 的舊程式碼:

ServicePointManager.DnsRefreshTimeout = 60000;
ServicePointManager.EnableDnsRoundRobin = true;
HttpWebRequest request = WebRequest.CreateHttp(uri);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

在較舊的 HttpWebRequest API 中,由於此功能的內建支援,因此啟用 DNS 循環配置資源相當簡單。 不過,較新 HttpClient API 不提供相同的內建功能。 儘管如此,您仍可以透過實施 DnsRoundRobinConnector 來實現類似行為,這會手動輪替 DNS 解析所傳回的 IP 位址。

使用 HttpClient 的新程式碼 :

// This is available as NuGet Package: https://www.nuget.org/packages/DnsRoundRobin/
// The original source code can be found also here: https://github.com/MihaZupan/DnsRoundRobin
public sealed class DnsRoundRobinConnector : IDisposable

您可以在這裡找到 DnsRoundRobinConnector 的實施情況。

DnsRoundRobinConnector使用情況:

private static readonly DnsRoundRobinConnector s_roundRobinConnector = new(
        dnsRefreshInterval: TimeSpan.FromSeconds(10),
        endpointConnectTimeout: TimeSpan.FromSeconds(5));
static async Task DnsRoundRobinConnectAsync()
{
    var handler = new SocketsHttpHandler
    {
        ConnectCallback = async (context, cancellation) =>
        {
            Socket socket = await DnsRoundRobinConnector.Shared.ConnectAsync(context.DnsEndPoint, cancellation);
            // Or you can create and use your custom DnsRoundRobinConnector instance
            // Socket socket = await s_roundRobinConnector.ConnectAsync(context.DnsEndPoint, cancellation);
            return new NetworkStream(socket, ownsSocket: true);
        }
    };
    var client = new HttpClient(handler);
    HttpResponseMessage response = await client.GetAsync(Uri);
}

範例:設定 SocketsHttpHandler 屬性

SocketsHttpHandler 是 .NET 中功能強大的彈性處理程式,可提供用於管理 HTTP 連線的進階設定選項。 藉由設定 SocketsHttpHandler 的各種屬性,您可以微調 HTTP 用戶端的行為,以符合特定需求,例如效能優化、安全性增強功能,以及自訂連線處理。

以下範例說明如何使用各種屬性來設定 SocketsHttpHandler,並將其與 HttpClient 搭配使用:

var cookieContainer = new CookieContainer();
cookieContainer.Add(new Cookie("cookieName", "cookieValue"));

var handler = new SocketsHttpHandler
{
    AllowAutoRedirect = true,
    AutomaticDecompression = DecompressionMethods.All,
    Expect100ContinueTimeout = TimeSpan.FromSeconds(1),
    CookieContainer = cookieContainer,
    Credentials = new NetworkCredential("user", "pass"),
    MaxAutomaticRedirections = 10,
    MaxResponseHeadersLength = 1,
    Proxy = new WebProxy("http://proxyserver:8080"), // Don't forget to set UseProxy
    UseProxy = true,
};

var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

範例:變更 ImpersonationLevel

此功能僅限於特定平台,而且有些過時。 如果您需要變通方法,可以參考此章節的程式碼

使用 HttpClient 時,您可能需要處理各種用途的用戶端憑證,例如伺服器憑證的自訂驗證或擷取伺服器證書。 HttpClient 提供數個屬性和選項,可有效地管理憑證。

範例:使用 SocketsHttpHandler 檢查憑證撤銷清單

SocketsHttpHandler.SslOptions 中的 CheckCertificateRevocationList 屬性可讓開發人員在 SSL/TLS 交握期間啟用或停用憑證撤銷清單 (CRL) 的檢查。 啟用此屬性可確保用戶端會驗證是否已撤銷伺服器的憑證,進而增強連線的安全性。

使用 HttpWebRequest 的舊程式碼:

ServicePointManager.CheckCertificateRevocationList = true;
HttpWebRequest request = WebRequest.CreateHttp(uri);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

使用 HttpClient 的新程式碼 :

bool checkCertificateRevocationList = true;
var handler = new SocketsHttpHandler
{
    SslOptions =
    {
        CertificateRevocationCheckMode = checkCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
    }
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

範例:擷取憑證

若要從 HttpClientRemoteCertificateValidationCallback 取得憑證,您可以使用 HttpClientHandlerSocketsHttpHandler.SslOptionsServerCertificateCustomValidationCallback 屬性。 此回呼可讓您在 SSL/TLS 交握期間檢查伺服器的憑證。

使用 HttpWebRequest 的舊程式碼:

HttpWebRequest request = WebRequest.CreateHttp(uri);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
X509Certificate? serverCertificate = request.ServicePoint.Certificate;

使用 HttpClient 的新程式碼 :

X509Certificate? serverCertificate = null;
var handler = new SocketsHttpHandler
{
    SslOptions = new SslClientAuthenticationOptions
    {
        RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
        {
            serverCertificate = certificate;

            // Leave the validation as-is.
            return sslPolicyErrors == SslPolicyErrors.None;
        }
    }
};
var client = new HttpClient(handler);
using var response = await client.GetAsync("https://example.com");

範例:啟用相互驗證

相互驗證也稱為雙向 SSL 或用戶端憑證驗證,是客戶端和伺服器彼此驗證的安全性程式。 這可確保雙方都是自稱的人,為敏感的通訊提供多一層安全保障。 在 HttpClient 中,您可以透過設定 HttpClientHandlerSocketsHttpHandler 以包含用戶端憑證並驗證伺服器的憑證,來啟用相互驗證。

若要啟用相互驗證,請執行以下步驟:

  • 載入用戶端憑證。
  • 設定 HttpClientHandler 或 SocketsHttpHandler 以包含用戶端憑證。
  • 如果需要自訂驗證,請設定伺服器憑證驗證回呼。

以下是 SocketsHttpHandler 的使用範例:

var handler = new SocketsHttpHandler
{
    SslOptions = new SslClientAuthenticationOptions
    {
        ClientCertificates = new X509CertificateCollection
        {
            // Load the client certificate from a file
            new X509Certificate2("path_to_certificate.pfx", "certificate_password")
        },
        RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
        {
            // Custom validation logic for the server certificate
            return sslPolicyErrors == SslPolicyErrors.None;
        }
    }
};

var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

標題屬性的使用方式

標題在 HTTP 通訊中扮演重要角色,提供有關要求和回應的基本中繼資料。 在 .NET 中使用 HttpClient 時,您可以設定和管理各種標題屬性,以控制 HTTP 要求和回應的行為。 了解如何有效地使用這些標題屬性,可協助您確保應用程式能有效率且安全地與 Web 服務通訊。

設定請求標題

請求標題可用來向伺服器提供有關所提出要求的其他資訊。 常見的使用案例包括指定內容類型、設定驗證權杖,以及新增自訂標題。 您可以使用 HttpClientDefaultRequestHeaders 屬性或 HttpRequestMessage 的標題屬性來設定請求標題。

範例:設定自訂請求標題

在 HttpClient 中設定預設自訂請求標題

var client = new HttpClient();
client.DefaultRequestHeaders.Add("Custom-Header", "value");

在 HttpRequestMessage 中設定自訂請求標題

var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Add("Custom-Header", "value");

範例:設定一般請求標題

在 .NET 中使用 HttpRequestMessage 時,設定一般請求標題對於向伺服器提供關於所提出要求的其他資訊至關重要。 這些標題可以包含驗證權杖等等。 正確設定這些標體可確保您的 HTTP 要求是由伺服器正確處理。 有關 HttpRequestHeaders 中可用的常見屬性完整清單,請參閱屬性

若要在 HttpRequestMessage 中設定常見的請求標題,您可以使用 HttpRequestMessage 物件的 Headers 屬性。 此屬性提供 HttpRequestHeaders 集合的存取權,您可以在其中視需要新增或修改標題。

在 HttpClient 中設定常見預設請求標題

using System.Net.Http.Headers;

var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "token");

在 HttpRequestMessage 中設定常見請求標題

using System.Net.Http.Headers;

var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "token");

範例:設定內容標題

內容標題可用來提供 HTTP 要求或回應主體的其他資訊。 在 .NET 中使用 HttpClient 時,您可以設定內容標題來指定與傳送或接收內容相關的媒體類型、編碼和其他中繼資料。 正確設定內容標題可確保伺服器和用戶端可以正確地解譯和處理內容。

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, uri);

// Create the content and set the content headers
var jsonData = "{\"key\":\"value\"}";
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");

// The following headers are set automatically by `StringContent`. If you wish to override their values, you can do it like so:
// content.Headers.ContentType = new MediaTypeHeaderValue("application/json; charset=utf-8");
// content.Headers.ContentLength = Encoding.UTF8.GetByteCount(jsonData);

// Assign the content to the request
request.Content = content;

using var response = await client.SendAsync(request);

範例:在 HttpClient 中設定 MaximumErrorResponseLength

MaximumErrorResponseLength 使用方式可讓開發人員指定處理程式將緩衝處理之錯誤回應內容的最大長度。 這適用於控制從伺服器收到錯誤回應時,讀取和儲存在記憶體中的資料量。 藉由使用這項技術,您可以在處理大型錯誤回應時防止過多的記憶體使用量,並改善應用程式的效能。

有幾種方法可以這麼做,我們將在此範例中檢查 TruncatedReadStream 技術:

internal sealed class TruncatedReadStream(Stream innerStream, long maxSize) : Stream
{
    private long _maxRemainingLength = maxSize;
    public override bool CanRead => true;
    public override bool CanSeek => false;
    public override bool CanWrite => false;

    public override long Length => throw new NotSupportedException();
    public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }

    public override void Flush() => throw new NotSupportedException();

    public override int Read(byte[] buffer, int offset, int count)
    {
        return Read(new Span<byte>(buffer, offset, count));
    }

    public override int Read(Span<byte> buffer)
    {
        int readBytes = innerStream.Read(buffer.Slice(0, (int)Math.Min(buffer.Length, _maxRemainingLength)));
        _maxRemainingLength -= readBytes;
        return readBytes;
    }

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
    }

    public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
    {
        int readBytes = await innerStream.ReadAsync(buffer.Slice(0, (int)Math.Min(buffer.Length, _maxRemainingLength)), cancellationToken)
            .ConfigureAwait(false);
        _maxRemainingLength -= readBytes;
        return readBytes;
    }

    public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
    public override void SetLength(long value) => throw new NotSupportedException();
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();

    public override ValueTask DisposeAsync() => innerStream.DisposeAsync();

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            innerStream.Dispose();
        }
    }
}

TruncatedReadStream 的使用範例:

int maxErrorResponseLength = 1 * 1024; // 1 KB

HttpClient client = new HttpClient();
using HttpResponseMessage response = await client.GetAsync(Uri);

if (response.Content is not null)
{
    Stream responseReadStream = await response.Content.ReadAsStreamAsync();
    // If MaxErrorResponseLength is set and the response status code is an error code, then wrap the response stream in a TruncatedReadStream
    if (maxErrorResponseLength >= 0 && !response.IsSuccessStatusCode)
    {
        responseReadStream = new TruncatedReadStream(responseReadStream, maxErrorResponseLength);
    }
    // Read the response stream
    Memory<byte> buffer = new byte[1024];
    int readValue = await responseReadStream.ReadAsync(buffer);
}

範例:套用 CachePolicy 標題

警告

HttpClient 沒有內建邏輯可快取回應。 除了自行實施所有快取之外,沒有因應措施。 只要設定標題將無法達到快取。

HttpWebRequest 移轉到 HttpClient 時,正確處理 pragmacache-control 等快取相關的標題非常重要。 這些標題會控制如何快取和擷取回應,確保應用程式在效能和資料新鮮度方面如預期般運作。

HttpWebRequest 中,您可能已經使用 CachePolicy 屬性來設定這些標題。 不過,在 HttpClient 中,您必須在要求上手動設定這些標題。

使用 HttpWebRequest 的舊程式碼:

HttpWebRequest request = WebRequest.CreateHttp(uri);
request.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

在較舊的 HttpWebRequest API 中,由於此功能的內建支援,所以套用 CachePolicy 相當簡單。 不過,較新 HttpClient API 不提供相同的內建功能。 儘管如此,您仍可透過實施 AddCacheControlHeaders 來達成相似的行為,手動新增快取相關的標題。

使用 HttpClient 的新程式碼 :

public static class CachePolicy
{
    public static void AddCacheControlHeaders(HttpRequestMessage request, RequestCachePolicy policy)

您可以在這裡找到 AddCacheControlHeaders 的實施情況。

AddCacheControlHeaders使用情況:

static async Task AddCacheControlHeaders()
{
    HttpClient client = new HttpClient();
    HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, Uri);
    CachePolicy.AddCacheControlHeaders(requestMessage, new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore));
    HttpResponseMessage response = await client.SendAsync(requestMessage);
}

緩衝屬性的使用方式

從 HttpWebRequest 移轉至 HttpClient 時,請務必了解這兩個 API 如何處理緩衝的差異。

使用 HttpWebRequest 的舊程式碼:

HttpWebRequest 中,您可以透過 AllowWriteStreamBufferingAllowReadStreamBuffering 屬性直接控制緩衝屬性。 這些屬性會啟用或停用從伺服器傳送和接收之資料的緩衝處理。

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.AllowReadStreamBuffering = true; // Default is `false`.
request.AllowWriteStreamBuffering = false; // Default is `true`.

使用 HttpClient 的新程式碼 :

HttpClient 中,沒有直接等同於 AllowWriteStreamBufferingAllowReadStreamBuffering 的屬性。

HttpClient 不會自行緩衝要求主體,而是將責任委派給所使用的 HttpContentStringContentByteArrayContent 等內容在邏輯上已經在記憶體中緩衝,而使用 StreamContent 預設不會產生任何緩衝。 若要強制緩衝處理內容,您可以在傳送要求之前呼叫 HttpContent.LoadIntoBufferAsync。 以下是範例:

HttpClient client = new HttpClient();

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = new StreamContent(yourStream);
await request.Content.LoadIntoBufferAsync();

HttpResponseMessage response = await client.SendAsync(request);

HttpClient 中,讀取緩衝預設為啟用。 若要避免,您可以指定 HttpCompletionOption.ResponseHeadersRead 標誌,或使用 GetStreamAsync 協助程式。

HttpClient client = new HttpClient();

using HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
await using Stream responseStream = await response.Content.ReadAsStreamAsync();

// Or simply
await using Stream responseStream = await client.GetStreamAsync(uri);