簡體   English   中英

將 DelegatingHandler 與 HttpClient 上的自定義數據一起使用

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM