C# 从手动管理的HttpClient(使用单独的TokenUri)迁移到IHttpClientFactory

C# 从手动管理的HttpClient(使用单独的TokenUri)迁移到IHttpClientFactory,c#,dotnet-httpclient,.net-core-3.1,C#,Dotnet Httpclient,.net Core 3.1,我有一个web应用程序缺少可用内存。 我怀疑HttpClient是问题之一,因为HttpClient的对象数随着时间的推移而增加 因此,我想迁移到托管IHttpClientFactory,但现在我一直在思考如何最好地实现对令牌服务的调用(我考虑使用) 目前,它的实施方式如下: var myClient = new MyClient(credentials, baseUri, tokenUri, timeout); 在MyClient内部,HttpClient(1)负责调用令牌服务(凭据、令牌U

我有一个web应用程序缺少可用内存。
我怀疑HttpClient是问题之一,因为HttpClient的对象数随着时间的推移而增加

因此,我想迁移到托管IHttpClientFactory,但现在我一直在思考如何最好地实现对令牌服务的调用(我考虑使用)

目前,它的实施方式如下:

var myClient = new MyClient(credentials, baseUri, tokenUri, timeout);
在MyClient内部,HttpClient(1)负责调用令牌服务(凭据、令牌URI)、存储到期日期并将承载令牌返回给调用端点(baseUri、超时)的HttpClient(2)
如果myClient现在尝试获取一些数据,它将检查令牌是否需要刷新,如果不需要,它将获取数据

我将如何使用IHttpClientFactory实现这一点
我是否仍然需要自己处理HttpClient(1)(到期日),或者工厂是否会检测到是否需要刷新令牌

我至少明白,工厂决定连接是否保持打开状态。

听起来您在正确的轨道上过渡到HttpClientFactory,尤其是类型化HttpClient

在后台,HttpClientFactory的默认实现管理底层主消息处理程序的池和处理,这意味着位于其上的实际HttpClient可以开始以作用域方式生成和处理,而不是试图管理一些全局消息处理程序,长期运行的it实例,或创建和拆除一次性实例,这在Microsoft自己的文档中有详细描述:

在像您这样的HttpClient可能存在很长时间的情况下,客户机本身管理其实例(如令牌)中的状态可能是有意义的,但您最终需要采取不同的路径,因为客户机可以(并且应该)更频繁地被处置

我是否仍然需要自己处理HttpClient(1)(到期日),或者工厂是否会检测到是否需要刷新令牌?

是的,您仍然需要处理它,但是HttpClientFactory模式提供了一些工具来帮助您管理它。由于您天生就倾向于使用HttpClientFactory进行依赖注入,因此您可能会走两条不同的道路

最基本的方法是添加某种单例令牌提供程序,为您管理令牌,并可通过DI容器注入类型化客户端:

public interface ITokenProvider
{
    string GetToken(string key);
    void StoreToken(string key, string token);
}

// Incredibly basic example, not thread safe, etc...
public class InMemoryTokenProvider : ITokenProvider
{
    private readonly Dictionary<string, string> _tokenList = new Dictionary<string, string>();

    public string GetToken(string key)
    {
        return _tokenList.GetValueOrDefault(key);
    }

    public void StoreToken(string key, string token)
    {
        _tokenList.Remove(key); // upsert, you get the point...
        _tokenList.Add(key, token);
    }
}

public class TypedClient
{
    private readonly HttpClient _client;
    private readonly ITokenProvider _tokenProvider;

    public TypedClient(HttpClient client, ITokenProvider tokenProvider)
    {
        _client = client;
        _tokenProvider = tokenProvider;
    }

    public async Task DoYourThing()
    {
        var token = _tokenProvider.GetToken("token_A");
        // ... if it failed, then UpdateTheAuth()
    }

    private async Task UpdateTheAuth()
    {
        var result = await _client.GetAsync("the auth process");
        string token = "whatever";
        // ...
        _tokenProvider.StoreToken("token_A", token);
    }
}
公共接口提供程序
{
字符串GetToken(字符串键);
void StoreToken(字符串键、字符串令牌);
}
//难以置信的基本示例,不是线程安全的,等等。。。
MemoryTokenProvider中的公共类:ITokenProvider
{
专用只读词典_tokenList=new Dictionary();
公共字符串GetToken(字符串键)
{
返回_tokenList.GetValueOrDefault(键);
}
公共void存储令牌(字符串密钥、字符串令牌)
{
_tokenList.Remove(key);//upsert,你得到了要点。。。
_tokenList.Add(key,token);
}
}
公共类TypedClient
{
私有只读HttpClient\u客户端;
私有只读ITokenProvider\u令牌提供商;
公共类型客户端(HttpClient客户端、ITokenProvider令牌提供程序)
{
_客户=客户;
_令牌提供者=令牌提供者;
}
公共异步任务DoYourThing()
{
var token=_tokenProvider.GetToken(“token_A”);
//…如果失败,则更新Auth()
}
私有异步任务updateAuth()
{
var result=await_client.GetAsync(“身份验证过程”);
string token=“无论如何”;
// ...
_tokenProvider.StoreToken(“token_A”,token);
}
}
当您在开始时进行服务注册并将令牌提供程序注册为单例时,您的所有状态(例如令牌)都不再是客户端本身的一部分,因此您的客户端现在可以在任何地方进行释放和注入。该提供者也可以被注销到缓存或数据库中

这可能仍然有点笨拙,因为它仍然将调用、失败、更新身份验证、重试等所有逻辑放在您的类型化客户端逻辑中——如果这能满足您的需要,可能就足够了,或者您可能需要更健壮的东西。HttpClientFactory可以轻松添加委派处理程序管道以及重试:

services.AddTransient<ExampleDelegatingHandler>();
services.AddHttpClient<IMyHttpClient, MyHttpClient>()
    .AddHttpMessageHandler<TokenApplicationHandler>()
    .AddPolicyHandler(GetRetryPolicy()); // see Microsoft link
services.AddTransient();
services.AddHttpClient()
.AddHttpMessageHandler()
.AddPolicyHandler(GetRetryPolicy());//请参阅Microsoft链接
委派处理程序管道连接到您的类型化客户机,并像中间件一样为每个请求和响应运行(并且可以在运行中修改它们),因此您甚至可以将部分令牌管理转移到委派处理程序中:

public class TokenApplicationHandler : DelegatingHandler
{
    private readonly ITokenProvider _tokenProvider;
    private readonly IAuthRenewerClient _authRenewer;

    public TokenApplicationHandler(ITokenProvider tokenProvider, IAuthRenewerClient authRenewer)
    {
        _tokenProvider = tokenProvider;
        _authRenewer = authRenewer;
    }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        // All just demo level, take the implementation with a grain of salt...
        string token = _tokenProvider.GetToken("token_A");
        request.Headers.Add("x-token-header", token);

        var response =  await base.SendAsync(request, cancellationToken);

        if (!response.IsSuccessStatusCode && response.StatusCode == HttpStatusCode.Unauthorized)
        {
            string newToken = _authRenewer.RefreshAuth();
            _tokenProvider.StoreToken("token_A", newToken);
        }

        return response;
    }
}
公共类TokenApplicationHandler:DelegatingHandler
{
私有只读ITokenProvider\u令牌提供商;
私有只读IAuthRenewerClient\u authrener;
公共TokenApplicationHandler(ITokenProvider tokenProvider、IAuthReneerClient AuthRenewar)
{
_令牌提供者=令牌提供者;
_authRenewer=authRenewer;
}
受保护的覆盖异步任务SendAsync(
HttpRequestMessage请求,
取消令牌(取消令牌)
{
//所有这些都只是演示级别,对实现持保留态度。。。
字符串令牌=_tokenProvider.GetToken(“令牌A”);
request.Headers.Add(“x-token-header”,token);
var response=await base.sendaync(请求、取消令牌);
如果(!response.issucessstatuscode&&response.StatusCode==HttpStatusCode.Unauthorized)
{
字符串newToken=\u authrener.RefreshAuth();
_tokenProvider.StoreToken(“token_A”,newToken);
}
返回响应;
}
}
与重试策略相结合,现在任何时候请求发出并返回
未经授权的