将 DelegatingHandler 与 HttpClient 上的自定义数据一起使用

Posted

技术标签:

【中文标题】将 DelegatingHandler 与 HttpClient 上的自定义数据一起使用【英文标题】:Using DelegatingHandler with custom data on HttpClient 【发布时间】:2021-02-18 14:08:12 【问题描述】:

鉴于众所周知的使用 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");

如何使用工厂实现类似的效果?

【问题讨论】:

您是否有理由要求新的客户端,而不是让框架通过依赖注入为您处理它?例如。 public void DoEnquiry(HttpClient client) @Jimenemex IHttpClientFactory 现在是大多数情况下的正确选择 (see here) 另一种解决方案是以某种方式关闭 HttpClient 的底层连接,以便释放套接字连接。但在这附近找不到任何东西 【参考方案1】:

我无法针对每个请求使用自定义数据实例化自定义 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");

【讨论】:

HttpRequestMessage.Properties 是我要找的,谢谢。 @stepen 我还有一个关于 HttpClient 和状态的问题,您可能可以提供帮助。 Intercept HttpClient with third party extensions using state 对于遇到此问题的其他人:在 .Net5 中,HttpRequestMessage.Properties 已弃用,您应该使用 .Options 属性。但是你会发现任何一种方式:) @Ssss:如果您手动创建拦截器,这是一个很好的构造函数。如果您使用的是IHttpClientFactory,则不会使用该构造函数。 @Ssss:当然。如果你包含一个无参数的构造函数,你还可以获得IHttpClientFactory 的好处。

以上是关于将 DelegatingHandler 与 HttpClient 上的自定义数据一起使用的主要内容,如果未能解决你的问题,请参考以下文章

WebApi 中响应的 DelegatingHandler

反向 NuGet 查找

在 ASP.NET Core Web API 中注册一个新的 DelegatingHandler

如果 ExceptionHandler 捕获到异常,则不会调用 DelegatingHandler

HttpClient DelegatingHandler 意外生命周期

添加 DelegatingHandler 后,WebAPI 2 出现间歇性 CORS 问题