mnj
mnj

Reputation: 3433

Use custom HttpMessageHandler with different configurations

I have a custom HttpMessageHandler implementation:

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
    }

}

It requires confiuration object MyHandlerOptions. Its form is not so important here. It basically contains clientId, clientSecret, etc. that are needed for the handler to know how to get the access token.

I have a few services (typed http clients) that need to use 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>();

The problem is that the MyHandlerOptions instance that I registered is valid only when used with Service1. However, Service2 and Service3 require other configuration (different clientId, clientSecret, etc.). How can I achieve it?

The possible solution that comes to my mind:

  1. Create a new service:
public class AccessTokenGetter 
{
    Task<string> GetAccessToken(AccessTokenConfig config) 
    {
        //get the access token...
    }
}
  1. Create separate HttpMessageHandlers for each case where configuration is different:
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. Register my services:
//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>();

Is there a better solution? I am not a great fan of my idea, it is not very flexible.

Upvotes: 3

Views: 2481

Answers (1)

Tiamo Idzenga
Tiamo Idzenga

Reputation: 1186

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

Upvotes: 5

Related Questions