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

查看:25
本文介绍了将 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);

调用样板相当难看.你可以把它包装成你自己的 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天全站免登陆