将DelegatingHandler与HttpClient上的自定义数据一起使用 [英] Using DelegatingHandler with custom data on HttpClient

查看:104
本文介绍了将DelegatingHandler与HttpClient上的自定义数据一起使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

鉴于众所周知的使用HttpClient的难题和问题-即套接字耗尽和不尊重DNS更新,它是使用IHttpClientFactory的最佳实践,并让容器决定何时以及如何利用http池连接效率.一切都很好,但是现在我无法在每个请求中实例化具有自定义数据的自定义DelegatingHandler.

Given the well known dilemma's and issues of using HttpClient - namely socket exhaustion and not respecting DNS updates, its considered best practice to use IHttpClientFactory and let the container decide when and how to utilise http pool connections efficiency. Which is all good, but now I cannot instantiate a custom DelegatingHandler with custom data on each request.

以下示例说明了在使用工厂方法之前我是如何做到的:

Sample below on how I did it before using the factory method:

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:

For every time I instantiate a HttpClient a Id can be passed along:

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或处理程序.

But now I cannot instantiate the HttpClient nor the Handlers.

public void DoEnquiry(IHttpClientFactory factory)
{
    int id = Database.InsertNewEnquiry();
    var http = factory.CreateClient();
    // and now??

    http.GetStringAsync("http://url.com");
}

使用工厂如何能够实现类似目标?

How would I be able to achieve similar using the factory?

推荐答案

我无法在每个请求上使用自定义数据实例化自定义DelegatingHandler.

I cannot instantiate a custom DelegatingHandler with custom data on each request.

这是正确的.但是您可以使用可重用(且无状态)的自定义 DelegatingHandler ,并将数据作为请求的一部分传递.这就是 HttpRequestMessage.Properties 用于.当进行这些类型的自定义上下文"时,在操作上,我更喜欢在上下文中定义我的上下文属性".作为扩展方法:

This is correct. But you can use a custom DelegatingHandler that is reusable (and stateless), and pass the data as part of the request. This is what HttpRequestMessage.Properties is for. When doing these kinds of "custom context" operations, I prefer to define my context "property" as an extension method:

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 中像这样使用它:

Then you can use it as such in a (reusable) 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 方法:

The disadvantage to this approach is that each callsite then has to specify the id. This means you can't use the built-in convenience methods like GetStringAsync; if you do, the exception above will be thrown. Instead, your code will have to use the lower-level SendAsync method:

var request = new HttpRequestMessage(HttpMethod.Get, "http://url.com");
request.SetId(id);
var response = await client.SendAsync(request);

调用样板非常难看.您可以将其包装到您的 own GetStringAsync 便捷方法中;像这样的东西应该工作:

The calling boilerplate is rather ugly. You can wrap this up into your own GetStringAsync convenience method; something like this should work:

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();
  }
}

现在您的呼叫站点再次看起来更加干净:

and now your call sites end up looking cleaner again:

public async Task DoEnquiry(IHttpClientFactory factory)
{
  int id = Database.InsertNewEnquiry();
  var http = factory.CreateClient();
  var result = await http.GetStringAsync(id, "http://url.com");
}

这篇关于将DelegatingHandler与HttpClient上的自定义数据一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆