Premek Vysoky
Premek Vysoky

Reputation: 101

Prevent IHttpClientFactory to create DI scope for handlers

  1. I am using scoped service called IOperationContextProvider to hold some information about my current execution context (called OperationContext).
  2. Whenever I start a new execution path (not only HTTP request, but some async impulses such as queue message, change feed change..), I create a dedicated DI service scope.
  3. Any class can inject the provider and has access to this context (such as correlation ID).

For outgoing requests, I would like to configure to add the correlation ID to outgoing HTTP header, like this:

services.AddHttpClient<IMyClass, MyClass>((serviceProvider, httpClient) =>
{
    var contextProvider = serviceProvider.GetRequiredService<IOperationContextProvider>();
    var corrId = contextProvider.Context.CorrelationId;
    httpClient.DefaultRequestHeaders.Add("x-corr-id", corrId);
});

However, I am unable to do this, because IHttpClientFactory creates scope for each handler it is creating and my context is not reachable from inside the HTTP client configuration. Same goes for adding HTTP message handlers, they are created in the same scope as the handler too.

Official documentation:

The IHttpClientFactory creates a separate DI scope for each handler. Handlers are free to depend upon services of any scope.

Is there any way to reach the same scope as in which the HttpClient itself is being built?

I only have found a way to where for the MyClass, where I also inject HttpClient, I inject the IOperationContextProvider too and configure manually the HttpClient but that is a bit cumbersome because it needs to be done everywhere:

public MyClass(HttpClient httpClient, IOperationContextProvider contextProvider)
{
    var corrId = contextProvider.Context.CorrelationId;
    httpClient.DefaultRequestHeaders.Add("x-corr-id", corrId);
    this._httpClient = httpClient;
}

Upvotes: 6

Views: 3984

Answers (2)

user4757113
user4757113

Reputation: 21

One of the things thats also suggested is that the shared settings like the defaultrequestheaders be properly setup to avoid race conditions, if you are planning to use this client as a shared resource. This is in reference to your initial proposed workaround.

public MyClass(HttpClient httpClient, IOperationContextProvider 
      contextProvider)
      {
           var corrId = contextProvider.Context.CorrelationId;
           httpClient.DefaultRequestHeaders.Add("x-corr-id", corrId);
           this._httpClient = httpClient;
       }

https://learn.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/

Upvotes: 2

poke
poke

Reputation: 387707

If you absolutely don’t want the HttpClientFactory to create a service scope, then you can disable this behavior through the HttpClientFactoryOptions.SuppressHandlerScope property. There isn’t a nice API to configure this though, so you will have to do something like this:

var httpClientBuilder = services.AddHttpClient<IMyClass, MyClass>(…);
services.Configure<HttpClientFactoryOptions>(httpClientBuilder.Name, options =>
{
    options.SuppressHandlerScope = true;
});

Alternatively, you could also create the delegating handler directly, without going through DI:

services.AddHttpClient<IMyClass, MyClass>(…)
    .AddHttpMessageHandler(sp =>
    {
        var contextProvider = sp.GetService<IOperationContextProvider>()
        return new MyHandlerWithoutDI(contextProvider);
    });

Upvotes: 3

Related Questions