[英]Using DelegatingHandler with custom data on HttpClient
鑒於眾所周知的使用 HttpClient 的困境和問題 - 即套接字耗盡和不尊重 DNS 更新,它被認為是使用 IHttpClientFactory 並讓容器決定何時以及如何使用 http 池連接效率的最佳實踐。 這一切都很好,但現在我無法在每個請求上使用自定義數據實例化自定義 DelegatingHandler。
下面的示例說明我在使用工廠方法之前是如何做到的:
public class HttpClientInterceptor : DelegatingHandler
{
private readonly int _id;
public HttpClientInterceptor(int id)
{
_id = id;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// associate the id with this request
Database.InsertEnquiry(_id, request);
return await base.SendAsync(request, cancellationToken);
}
}
每次我實例化一個 HttpClient 時,都可以傳遞一個 Id:
public void DoEnquiry()
{
// Insert a new enquiry hypothetically
int id = Database.InsertNewEnquiry();
using (var http = new HttpClient(new HttpClientInterceptor(id)))
{
// and do some operations on the http client
// which will be logged in the database associated with id
http.GetStringAsync("http://url.com");
}
}
但現在我無法實例化 HttpClient 或處理程序。
public void DoEnquiry(IHttpClientFactory factory)
{
int id = Database.InsertNewEnquiry();
var http = factory.CreateClient();
// and now??
http.GetStringAsync("http://url.com");
}
我如何能夠使用工廠實現類似的目標?
我無法在每個請求上使用自定義數據實例化自定義 DelegatingHandler。
這是對的。 但是您可以使用可重用(且無狀態)的自定義DelegatingHandler
,並將數據作為請求的一部分傳遞。 這就是HttpRequestMessage.Properties
的用途。 在進行這些類型的“自定義上下文”操作時,我更喜歡將我的上下文“屬性”定義為擴展方法:
public static class HttpRequestExtensions
{
public static HttpRequestMessage WithId(this HttpRequestMessage request, int id)
{
request.Properties[IdKey] = id;
return request;
}
public static int? TryGetId(this HttpRequestMessage request)
{
if (request.Properties.TryGetValue(IdKey, out var value))
return value as int?;
return null;
}
private static readonly string IdKey = Guid.NewGuid().ToString("N");
}
然后你可以在(可重用的) DelegatingHandler
中使用它:
public class HttpClientInterceptor : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var id = request.TryGetId();
if (id == null)
throw new InvalidOperationException("This request must have an id set.");
// associate the id with this request
Database.InsertEnquiry(id.Value, request);
return await base.SendAsync(request, cancellationToken);
}
}
這種方法的缺點是每個調用站點都必須指定 id。 這意味着您不能使用像GetStringAsync
這樣的內置便利方法; 如果你這樣做,上面的異常將被拋出。 相反,您的代碼將不得不使用較低級別的SendAsync
方法:
var request = new HttpRequestMessage(HttpMethod.Get, "http://url.com");
request.SetId(id);
var response = await client.SendAsync(request);
調用樣板相當難看。 您可以將其包裝到您自己的GetStringAsync
便捷方法中; 像這樣的東西應該工作:
public static class HttpClientExtensions
{
public static async Task<string> GetStringAsync(int id, string url)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.SetId(id);
var response = await client.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
}
現在您的呼叫站點最終看起來又干凈了:
public async Task DoEnquiry(IHttpClientFactory factory)
{
int id = Database.InsertNewEnquiry();
var http = factory.CreateClient();
var result = await http.GetStringAsync(id, "http://url.com");
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.