如何通过依赖注入使用 RateLimiter 的 HttpClient DelegatingHandler?

Luk*_*oen 2 c# dependency-injection dotnet-httpclient .net-core

我已经在我的项目中成功使用RateLimiter ( github ) 一段时间了。我最近发现了依赖注入,并尝试按原样迁移我的代码以使用它,但我陷入了 RateLimiter。

文档中的正常用法是

var handler = TimeLimiter
        .GetFromMaxCountByInterval(25, TimeSpan.FromMinutes(1))
        .AsDelegatingHandler();
var Client = new HttpClient(handler)
Run Code Online (Sandbox Code Playgroud)

但是,如果我尝试在依赖注入期间复制它

    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services.AddHttpClient<IMyApiClient, MyApiClient>(client => client.BaseAddress = new Uri("https://api.myapi.com/"))
               .AddPolicyHandler(GetRetryPolicy())
               .AddHttpMessageHandler(() => TimeLimiter.GetFromMaxCountByInterval(25, TimeSpan.FromMinutes(1)).AsDelegatingHandler());
     }
Run Code Online (Sandbox Code Playgroud)

我收到错误:

[2021-05-17T21:58:00.116Z] Microsoft.Extensions.Http: The 'InnerHandler' property must be null. 'DelegatingHandler' instances provided to 'HttpMessageHandlerBuilder' must not be reused or cached.
[2021-05-17T21:58:00.117Z] Handler: 'ComposableAsync.DispatcherDelegatingHandler'.
[2021-05-17T21:58:00.122Z] An unhandled host error has occurred.
Run Code Online (Sandbox Code Playgroud)

我的类客户端结构(主要是为了单元测试)如下所示:

using System.Net.Http;
using System.Threading.Tasks;

public class MyApiClient : HumbleHttpClient, IMyApiClient
{
    public MyApiClient(HttpClient client)
        : base(client)
    {
    }
}

public class HumbleHttpClient : IHttpClient
{
    public HumbleHttpClient(HttpClient httpClient)
    {
        this.Client = httpClient;
    }

    public HttpClient Client { get; }

    public Task<HttpResponseMessage> GetAsync(string requestUri)
    {
        return this.Client.GetAsync(requestUri);
    }

    public Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content)
    {
        return this.Client.PostAsync(requestUri, content);
    }
}

public interface IHttpClient
{
    Task<HttpResponseMessage> GetAsync(string requestUri);
    Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content);
    HttpClient Client { get; }
}
Run Code Online (Sandbox Code Playgroud)

这些是我一直在尝试遵循的文档: https: //learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-要求

And*_*ndy 5

我不知道这是一个错误还是一个功能,但要解决这个问题,我相信您还需要将委托处理程序添加为 Transient。

DelegatingHandler因此,在典型情况下,您可以这样设置:

public sealed class MyDelegatingHandler : DelegatingHandler
{
    // ...
}
Run Code Online (Sandbox Code Playgroud)

然后你需要注册它;但这是问题......您还需要将处理程序注册为 Transient:

public override void Configure(IFunctionsHostBuilder builder)
{
    builder.Services.AddHttpClient<IMyHttpService, MyHttpService>()
        .AddHttpMessageHandler<MyDelegatingHandler>();

    // this needs to be added:
    builder.Services.AddTransient<MyDelegatingHandler>();
}
Run Code Online (Sandbox Code Playgroud)

话虽如此,您处于一个独特的情况,因为您想要使用的处理程序是在运行时从第 3 方创建的。你可以让这个工作......如果它是正确的,不是100%,但它应该工作:

public override void Configure(IFunctionsHostBuilder builder)
{
    var handler = TimeLimiter
        .GetFromMaxCountByInterval(25, TimeSpan.FromMinutes(1))
        .AsDelegatingHandler();

    builder.Services.AddHttpClient<IMyApiClient, MyApiClient>(
            client => client.BaseAddress = new Uri("https://api.myapi.com/"))
        .AddPolicyHandler(GetRetryPolicy())
        .AddHttpMessageHandler(() => handler);

    builder.Services.AddTransient(_ => handler);
}
Run Code Online (Sandbox Code Playgroud)

编辑添加...

您看到的问题与ComposableAsyncNuget 包有关。具体是这一行。代码不应设置此属性。ASP.NET Core 应该/将会做到这一点。幸运的是,这只是一个简单的扩展方法。

让我们编写自己的扩展来以正确的方式执行此操作:

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace YourNamespace
{
    public static class DispatcherExtension
    {
        private sealed class DispatcherDelegatingHandler : DelegatingHandler
        {
            private readonly ComposableAsync.IDispatcher _Dispatcher;

            public DispatcherDelegatingHandler(ComposableAsync.IDispatcher dispatcher)
            {
                _Dispatcher = dispatcher;
            }

            protected override Task<HttpResponseMessage> SendAsync(
                HttpRequestMessage request,
                CancellationToken cancellationToken)
            {
                return _Dispatcher.Enqueue(() =>
                    base.SendAsync(request, cancellationToken), cancellationToken);
            }
        }

        public static DelegatingHandler AsDelegatingHandler(
            this ComposableAsync.IDispatcher dispatcher)
        {
            return new DispatcherDelegatingHandler(dispatcher);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

确保将您的代码更改为我上面注册处理程序的方式。另外,请确保它使用新的扩展而不是库中的扩展。

我测试了它,你可以看到它设置正确:

处理程序注入

我还会考虑在 ComposableAsync github 页面中打开一个错误。