在 ASP.NET 核心中访问 HttpClientFactory 定义中的外部参数/值

Posted

技术标签:

【中文标题】在 ASP.NET 核心中访问 HttpClientFactory 定义中的外部参数/值【英文标题】:Accessing external parameters/values within HttpClientFactory definition in ASP.NET core 【发布时间】:2021-10-16 15:06:32 【问题描述】:

我有一个使用 Polly 策略的 httpclient:

services.AddHttpClient("MyClient", (serviceProvider, client) =>
        
            var X = External_PARAMETER;
            client.BaseAddress = new Uri(settings.BaseUrl);
            .....
        )
        .AddPolicyHandler(retryPolicy)
        .AddPolicyHandler((provider, httpRequestMessage) => GetTokenPolicy(provider, httpRequestMessage));

当从这个命名的客户端创建 clienthttp 时,我想动态地传递一些参数给它,比如:

var httpClient = httpClientFactory.CreateClient("MyClient", External_PARAMETER);

我知道一种解决方案实际上是使用这样的单例服务并传递值: 据我所知,您不能在 AddHttpClient 定义中使用瞬态或范围服务。如果我使用非 sington 的,我会收到此错误(无法从根提供商解析范围服务“MyApp.IMyService”。))

services.AddHttpClient("MyClient", (serviceProvider, client) =>
        
            var passedServer = serviceProvider.GetService<IMyService>();
            var X = passedServer.GetMyParameter();
            client.BaseAddress = new Uri(settings.BaseUrl);
            .....
        )
        .AddPolicyHandler(retryPolicy)
        .AddPolicyHandler((provider, httpRequestMessage) => GetTokenPolicy(provider, httpRequestMessage));
services.AddSingleton<IMyService, MyService>();
.
.
.
myServiceInstance.SetMyParameter("A_VALUE");
var httpClient = httpClientFactory.CreateClient("MyClient");

但是如果不使用像 MyService 这样的单例服务,我该如何实现呢?

在某些情况下,HTTP 请求应该创建一个 HttpClient 并且拥有一个 sington 服务意味着所有请求都可以访问这个不理想的单例服务。

【问题讨论】:

你为什么不在CreateClient的结果上设置那个值? 【参考方案1】:

据我所知,我们可以尝试为httpclient设置DelegatingHandler,这个DelegatingHandler可以注册为Transient。

更多细节,你可以参考下面的例子:

处理程序:

public class TraceLogHandler : DelegatingHandler

    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<TraceLogHandler> _logger;
    private readonly bool _canLog;
    private StringValues ipAddress;

    public TraceLogHandler(IHttpContextAccessor httpContextAccessor, ILogger<TraceLogHandler> logger)
    
        _httpContextAccessor = httpContextAccessor;
        _logger = logger;
    

    public TraceLogHandler(IHttpContextAccessor httpContextAccessor, ILogger<TraceLogHandler> logger, bool canLog)
    
        _httpContextAccessor = httpContextAccessor;
        _logger = logger;
        _canLog = canLog;
    

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    
        // here you could modify the http request
        int i = 0;

        return new HttpResponseMessage();
        //blah blah code for custom logging
    

Startup.cs:

services.AddTransient<TraceLogHandler>();
services.AddHttpClient<ICountryRepositoryClient, CountryRepositoryClientV2>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://localhost"))
    .AddHttpMessageHandler((services) =>
    
        return new TraceLogHandler(services.GetRequiredService<IHttpContextAccessor>(),
                                   services.GetRequiredService<ILogger<TraceLogHandler>>());
    );

【讨论】:

对不起,但我不明白这如何回答 OP 的问题。您能否从这方面扩展您的答案? OP 要求是动态地将一些参数传递给clienthttp。但他无法使用 Transient 服务获取参数并添加到其中。但是 AddHttpClient 服务有 AddHttpMessageHandler 可以在每次 httpclient 想要发送请求时修改 httpclient。在此期间,他可以使用处理程序修改 http 标头或任何内容。此外,这个处理程序可以注册为瞬态。他可以修改服务以添加一些参数或将一些参数传递给发送方法,该服务将是瞬态的。【参考方案2】:

您可以尝试使用 .NET 中的 Options pattern。

它允许您创建和访问一组命名的单例设置,而不是创建一个提供值的单例服务,因此您可以为每个命名的 HTTP 客户端创建一个设置:

class MyHttpClientOptions

    public string BaseUri  get; set; 


services.Configure<MyHttpClientOptions>("MyClient", opt =>

    opt.BaseUri = "https://url1";
);

services.Configure<MyHttpClientOptions>("MyClient2", opt =>

    opt.BaseUri = "https://url2";
);

您可以在创建HttpClient 时访问命名设置:

services.AddHttpClient("MyClient", (serviceProvider, client) =>

    var options = serviceProvider
        .GetRequiredService<IOptionsMonitor<MyHttpClientOptions>>()
        .Get("MyClient");

    client.BaseAddress = new Uri(options.BaseUri);
);

更新

如果某些值确实必须从请求的上下文中获取,并且无法设置到创建它的客户端,您可以添加一个使用 IHttpContextAccessor 的自定义构建器:

services.AddHttpClient("MyClient1");
services.AddOptions<HttpClientFactoryOptions>("MyClient1")
    .Configure<IHttpContextAccessor>((opt, httpContextAccessor) =>
    
        opt.HttpClientActions.Add(client =>
        
            var httpContext = httpContextAccessor.HttpContext;

            // If you have scoped service to create in the request context
            httpContext.RequestServices.GetRequiredService<MyScopedService>();

            // Shared key value pairs
            httpContext.Items["some key"]
        );
    );

【讨论】:

但是在创建 httpclient (client = httpClientFactory.CreateClient("MyClient");) 时如何传递一些值?所以在我定义命名客户端“services.AddHttpClient(...)”时,我不知道这些值。 如果是这种情况,为什么要将值传递给客户端工厂?为什么不将值设置为创建的客户端?客户端具有短暂的生命周期,因此将值设置为一个不会影响其他值。 谢谢,是的,它是正确的。但是如何将值传递给客户端工厂,然后在 AddHttpClient 代码中引用它? 如果您确实需要将作用域参数传递给 HTTP 客户端工厂,您可能需要在 AddHttpClient 代码中使用 IHttpContextAccessor 来访问请求的上下文并从上下文中获取值。跨度> 太棒了!使用 httpcontext 是一个好主意,并且可以按我的预期工作。在访问这个名为 httpclient 时,我还有另一个用例。它实际上是作为 dll 文件公开的,也可以在后台进程中使用。有多个同时运行的 cron 作业也将访问这个命名的 httpclient。在进程或 backgorund 作业(托管服务/Cron 作业)中创建 httpclient 时,看起来 httpcontext 不存在。知道如何解决这个问题吗?谢谢

以上是关于在 ASP.NET 核心中访问 HttpClientFactory 定义中的外部参数/值的主要内容,如果未能解决你的问题,请参考以下文章

如何使用带有 OpenId Connect 的刷新令牌在 asp.net 核心中处理过期的访问令牌

ASP.NET 核心中是不是支持刷新令牌?

json Asp.net核心访问appsettings.json

在 asp.net 核心中使用返回 url

ASP.NET 核心 1.0。 Bearer Token,无法访问自定义声明

asp.net的核心是啥