【问题标题】:How can I use RateLimiter's HttpClient DelegatingHandler with dependency injection?如何将 RateLimiter 的 HttpClient DelegatingHandler 与依赖注入一起使用?
【发布时间】:2021-05-17 22:12:12
【问题描述】:

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

文档中的正常用法是

var handler = TimeLimiter
        .GetFromMaxCountByInterval(25, TimeSpan.FromMinutes(1))
        .AsDelegatingHandler();
var Client = new HttpClient(handler)

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

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

我收到错误:

[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.

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

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

这些是我一直在努力关注的文档: https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests

【问题讨论】:

    标签: c# .net-core dependency-injection dotnet-httpclient


    【解决方案1】:

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

    因此,在典型情况下,您会这样设置DelegatingHandler

    public sealed class MyDelegatingHandler : DelegatingHandler
    {
        // ...
    }
    

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

    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services.AddHttpClient<IMyHttpService, MyHttpService>()
            .AddHttpMessageHandler<MyDelegatingHandler>();
    
        // this needs to be added:
        builder.Services.AddTransient<MyDelegatingHandler>();
    }
    

    话虽如此,您处于一种独特的情况,因为您要使用的处理程序是在运行时从第 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);
    }
    

    编辑添加...

    您看到的问题与ComposableAsync Nuget 包有关。特别是this line。代码不应设置此属性。 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);
            }
        }
    }
    

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

    我对其进行了测试,您可以看到它设置正确:

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

    【讨论】:

    • 这是个好主意,但我仍然遇到错误“InnerHandler 属性不能为空”。我认为我正在使用的 nuget 包有问题。在他们的构造函数中,他们明确设置了 InnerHandler,这可能是这个问题的根源。公共 DispatcherDelegatingHandler(IDispatcher 调度程序) { this._Dispatcher = dispatcher; this.InnerHandler = (HttpMessageHandler) 新的 HttpClientHandler(); }
    • @LukeSchoen -- 所以问题是this line。他不应该这样设置。 ASP.NET Core 设置了这一点。
    • @LukeSchoen -- 更新了答案。那应该可以解决它。
    猜你喜欢
    • 1970-01-01
    • 2013-01-23
    • 2018-02-22
    • 1970-01-01
    • 1970-01-01
    • 2021-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多