[英]Using DelegatingHandler with custom data on HttpClient
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.鉴于众所周知的使用 HttpClient 的困境和问题 - 即套接字耗尽和不尊重 DNS 更新,它被认为是使用 IHttpClientFactory 并让容器决定何时以及如何使用 http 池连接效率的最佳实践。 Which is all good, but now I cannot instantiate a custom DelegatingHandler with custom data on each request.
这一切都很好,但现在我无法在每个请求上使用自定义数据实例化自定义 DelegatingHandler。
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);
}
}
For every time I instantiate a HttpClient a Id can be passed along:每次我实例化一个 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");
}
}
But now I cannot instantiate the HttpClient nor the Handlers.但现在我无法实例化 HttpClient 或处理程序。
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?我如何能够使用工厂实现类似的目标?
I cannot instantiate a custom DelegatingHandler with custom data on each request.
我无法在每个请求上使用自定义数据实例化自定义 DelegatingHandler。
This is correct.这是对的。 But you can use a custom
DelegatingHandler
that is reusable (and stateless), and pass the data as part of the request.但是您可以使用可重用(且无状态)的自定义
DelegatingHandler
,并将数据作为请求的一部分传递。 This is what HttpRequestMessage.Properties
is for.这就是
HttpRequestMessage.Properties
的用途。 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");
}
Then you can use it as such in a (reusable) DelegatingHandler
:然后你可以在(可重用的)
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);
}
}
The disadvantage to this approach is that each callsite then has to specify the id.这种方法的缺点是每个调用站点都必须指定 id。 This means you can't use the built-in convenience methods like
GetStringAsync
;这意味着您不能使用像
GetStringAsync
这样的内置便利方法; if you do, the exception above will be thrown.如果你这样做,上面的异常将被抛出。 Instead, your code will have to use the lower-level
SendAsync
method:相反,您的代码将不得不使用较低级别的
SendAsync
方法:
var request = new HttpRequestMessage(HttpMethod.Get, "http://url.com");
request.SetId(id);
var response = await client.SendAsync(request);
The calling boilerplate is rather ugly.调用样板相当难看。 You can wrap this up into your own
GetStringAsync
convenience method;您可以将其包装到您自己的
GetStringAsync
便捷方法中; 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");
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.