使用具有不同配置的自定义HttpMessageHandler

问题描述

我有一个自定义HttpMessageHandler实现:

public class MyHandler : DelegatingHandler
{
    private readonly HttpClient _httpClient;
    private readonly MyHandlerOptions _config;

    public MyHandler(
        HttpClient httpClient,IOptions<MyHandlerOptions> options)
    {
        _httpClient = httpClient;
        _config = options.Value;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
    {
        request.Headers.Authorization = await GetAccesstoken()
        return await base.SendAsync(request,cancellationToken);
    }

    private async Task<string> GetAccesstoken()
    {
        //some logic to get access token using _httpClient and _config
    }

}

它需要配置对象MyHandlerOptions。它的形式在这里不是很重要。它基本上包含clientId,clientSecret等,这些是处理程序知道如何获取访问令牌所需的。

我有一些服务(类型为http客户端)需要使用MyHandler

//registration of MyHandler itself
builder.Services.AddHttpClient<MyHandler>();

//configuration of MyHandler
builder.Services.AddOptions<MyHandlerOptions>()
.Configure<IConfiguration>((config,configuration) =>
{
    configuration.GetSection("MyHandlerOptions").Bind(config);
});

//Services that need to use MyHandler:
services.AddHttpClient<Service1>()
    .AddHttpMessageHandler<MyHandler>();
services.AddHttpClient<Service2>()
    .AddHttpMessageHandler<MyHandler>();
services.AddHttpClient<Service3>()
    .AddHttpMessageHandler<MyHandler>();

问题是我注册MyHandlerOptions实例仅在与Service1一起使用时才有效。但是,Service2Service3需要其他配置(不同的clientId,clientSecret等)。我该如何实现?

我想到的可能的解决方案:

  1. 创建新服务:
public class AccesstokenGetter 
{
    Task<string> GetAccesstoken(AccesstokenConfig config) 
    {
        //get the access token...
    }
}
  1. 针对每种配置不同的情况分别创建HttpMessageHandler
public class MyHandler1 : DelegatingHandler
{
    private readonly MyHandler1Options _config;
    private readonly AccesstokenGetter _accesstokenGetter;

    public MyHandler(AccesstokenGetter accesstokenGetter,IOptions<MyHandlerOptions1> options)
    {
        _accesstokenGetter = accesstokenGetter;
        _config = options.Value;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
    {
        //somehow convert _config to AccesstokenConfig
        request.Headers.Authorization = await _accesstokenGetter.GetAccesstoken(_config)
        return await base.SendAsync(request,cancellationToken);
    }
}

public class MyHandler2 : DelegatingHandler
{
    //same implementation as MyHandler1,just use MyHandler2Options instead
}
  1. 注册我的服务:
//configurations
builder.Services.AddOptions<MyHandler1Options>()
.Configure<IConfiguration>((config,configuration) =>
{
    configuration.GetSection("MyHandler1Options").Bind(config);
});

builder.Services.AddOptions<MyHandler2Options>()
.Configure<IConfiguration>((config,configuration) =>
{
    configuration.GetSection("MyHandler2Options").Bind(config);
});

//AccesstokenGetter
services.AddHttpClient<AccesstokenGetter>()

//Services that need to use MyHandlers:
services.AddHttpClient<Service1>()
    .AddHttpMessageHandler<MyHandler1>();
services.AddHttpClient<Service2>()
    .AddHttpMessageHandler<MyHandler2>();
services.AddHttpClient<Service3>()
    .AddHttpMessageHandler<MyHandler2>();

有更好的解决方案吗?我不是这个想法的忠实拥护者,也不是很灵活。

解决方法

services.AddHttpClient<Service1>()
    .AddHttpMessageHandler(sp => 
    {
         var handler = sp.GetRequiredService<MyHandler>();
         handler.Foo = "Bar";
         return handler;
    });

services.AddHttpClient<Service2>()
    .AddHttpMessageHandler(sp => 
    {
         var handler = sp.GetRequiredService<MyHandler>();
         handler.Foo = "Baz";
         return handler;
    });