Exploring the code behind IHttpClientFactory in depth
Tags: aspnetcore, httpclient, dependencyinjection, sourcecodedive
Published: 2020-08-16 16:02:35

---
title: Exploring the code behind IHttpClientFactory in depth
published: true
date: 2020-08-04 10:00:00 UTC
tags: ASPNETCore,HttpClient,DependencyInjection,SourceCodeDive
canonical_url: https://andrewlock.net/exporing-the-code-behind-ihttpclientfactory/
---

 ![Exploring the code behind IHttpClientFactory in depth](https://andrewlock.net/content/images/2020/httpclientfactory_banner.png)

In this post I take a look at the code in the default implementation of `IHttpClientFactory` in ASP.NET Core—`DefaultHttpClientFactory`. We'll see how it ensures that `HttpClient` instances created with the factory [prevent socket exhaustion](https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/), while also ensuring that [DNS changes are respected](http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html).

> This post assumes you already have a general idea of `IHttpClientFactory` and what it's used for, so if it's new to you, take a look at [Steve Gordon's series on IHttpClientFactory](https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore), or [see the docs](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests).

The code in this post is based on [the HEAD of the master branch](https://github.com/dotnet/runtime/blob/065bf96e5c298352c790b9a82564d57a745725f5/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs) of [https://github.com/dotnet/runtime](https://github.com/dotnet/runtime) at the time of writing, after .NET 5 preview 7. But the code hasn't changed significantly for 2 years, so I don't expect it to change much soon!

> I've taken a couple of liberties with the code by removing null checks, in-lining some trivial methods, and removing some code that's tangential to this discussion. For the original code, [see GitHub](https://github.com/dotnet/runtime/blob/065bf96e5c298352c790b9a82564d57a745725f5/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs).

## A brief overview of IHttpClientFactory

`IHttpClientFactory` allows you to create `HttpClient` instances for interacting with HTTP APIs, using best practices to avoid common issues. Before `IHttpClientFactory`, it was common to fall into one of two traps when creating `HttpClient` instances:

- _Create and dispose of new `HttpClient` instances as required_. [This can lead to socket exhaustion](https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/) due to the TIME\_WAIT period required after closing a connection.
- _Create a singleton `HttpClient` for the lifetime of the application_. This can lead to [issues when the DNS record for the HTTP API you're using changes](http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html)—the changes will not be respected by the `HttpClient`.

`IHttpClientFactory` was added in .NET Core 2.1, and solves this issue by separating the management of `HttpClient`, from the the `HttpMessageHandler` chain that is used to send the message. In reality, it is the lifetime of the `HttpClientHandler` at the _end_ of the pipeline that is the important thing to manage, as this is the handler that actually makes the connection

![HttpClient and HttpClientHandler pipeline](https://andrewlock.net/content/images/2020/httpclientfactory_01.png)

In addition to simply managing the handler lifetimes, `IHttpClientFactory` also makes it easy to customise the generated `HttpClient` and message handler pipeline using an `IHttpClientBuilder`. This allows you to "pre-configure" a named of typed `HttpClient` that is created using the factory, for example to set the base address or add default headers:

```csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("github", c =>
    {
        c.BaseAddress = new Uri("https://api.github.com/");
    })
    .ConfigureHttpClient(c => 
    {
        c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
        c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
    });
}
```

You can add multiple configuration functions at the `HttpClient` level, but you can _also_ add additional `HttpMessageHandler`s to the pipeline. Steve shows how you can create your own handlers in [in his series](https://www.stevejgordon.co.uk/httpclientfactory-aspnetcore-outgoing-request-middleware-pipeline-delegatinghandlers). To add message handlers to a named client, use `IHttpClientBuilder.AddHttpMessageHandler<>`, and register the handler with the DI container:

```csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("github", c =>
    {
        c.BaseAddress = new Uri("https://api.github.com/");
    })
    .AddHttpMessageHandler<TimingHandler>() // This handler is on the outside and executes first on the way out and last on the way in.
    .AddHttpMessageHandler<ValidateHeaderHandler>(); // This handler is on the inside, closest to the request.

    // Add the handlers to the service collection
    services.AddTransient<TimingHandler>();
    services.AddTransient<ValidateHeaderHandler>();
}
```

When you call `ConfigureHttpClient()` or `AddHttpMessageHandler()` to configure your HttpClient, you're actually adding configuration messages to a named `IOptions` instance, `HttpClientFactoryOptions`. [You can read more about _named options_ here](https://andrewlock.net/using-multiple-instances-of-strongly-typed-settings-with-named-options-in-net-core-2-x/), but the details aren't too important for this post.

That handles _configuring_ the `IHttpClientFactory`. To use the factory, and create an `HttpClient`, you first obtain an instance of the singleton `IHttpClientFactory`, and then you call `CreateClient(name)`, providing the name of the client to create.

> If you don't provide a name to `CreateClient()`, the factory will use the default name, `""` (the empty string).

```csharp
public class MyService
{
    // IHttpClientFactory is a singleton, so can be injected everywhere
    private readonly IHttpClientFactory _factory;
    public MyService(IHttpClientFactory factory)
    {
        _factory = factory;
    }

    public async Task DoSomething()
    {
        // Get an instance of the typed client
        HttpClient client = _factory.CreateClient("github");

        // Use the client...
    }
}
```

The remainder of this post focus on what happens behind the scenes when you call `CreateClient()`.

## Creating an HttpClient and HttpMessageHandler

The `CreateClient()` method, shown below, is how you typically interact with the `IHttpClientFactory`. I discuss this method below.

```csharp
// Injected in constructor
private readonly IOptionsMonitor<HttpClientFactoryOptions> _optionsMonitor

public HttpClient CreateClient(string name)
{
    HttpMessageHandler handler = CreateHandler(name);
    var client = new HttpClient(handler, disposeHandler: false);

    HttpClientFactoryOptions options = _optionsMonitor.Get(name);
    for (int i = 0; i < options.HttpClientActions.Count; i++)
    {
        options.HttpClientActions[i](client);
    }

    return client;
}
```

This is a relatively simple method on the face of it. We start by creating the `HttpMessageHandler` pipeline by calling `CreateHandler(name)`, and passing in the name of the client to create. We'll look into that method shortly, as it's where most of the magic happens around handler pooling and lifetimes.

Once you have a handler, a new instance of `HttpClient` is created and passed to the handler. The important thing to note is the `disposeHandler: false` argument. This ensures that disposing the `HttpClient` _doesn't_ dispose the handler pipeline, as the `IHttpClientFactory` will handle that itself.

Finally, the latest `HttpClientFactoryOptions` for the named client are fetched from the `IOptionsMonitor` instance. This contains the configuration functions for the `HttpClient` that were added in `Startup.ConfigureServices()`, and sets things like the `BaseAddress` and default headers.

> [I discussed using `IOptionsMonitor` in a previous post](https://andrewlock.net/creating-singleton-named-options-with-ioptionsmonitor/). It is useful when you want to load named options in a singleton context, where you can't use the simpler `IOptionsSnapshot` interface. It also has other change-detection capabilities that aren't used in this case.

Finally, the `HttpClient` is returned to the caller. Let's look at the `CreateHandler()` method now and see how the `HttpMessageHandler` pipeline is created. There's quite a few layers to get through, so we'll walk through it step-by-step.

```csharp
// Created in the constructor
readonly ConcurrentDictionary<string, Lazy<ActiveHandlerTrackingEntry>> _activeHandlers;;

readonly Func<string, Lazy<ActiveHandlerTrackingEntry>> _entryFactory = (name) =>
    {
        return new Lazy<ActiveHandlerTrackingEntry>(() =>
        {
            return CreateHandlerEntry(name);
        }, LazyThreadSafetyMode.ExecutionAndPublication);
    };

public HttpMessageHandler CreateHandler(string name)
{
    ActiveHandlerTrackingEntry entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value;

    entry.StartExpiryTimer(_expiryCallback);

    return entry.Handler;
}
```

The `CreateHandler()` method does two things:

- It gets or creates an `ActiveHandlerTrackingEntry`
- It stars a timer on the entry

The `_activeHandlers` field is a `ConcurrentDictionary<>`, keyed on the name of the client (e.g. `"gitHub"`). The dictionary values are `Lazy<ActiveHandlerTrackingEntry>`. Using `Lazy<>` here is [a neat trick I've blogged about previously to make the `GetOrAdd` function thread safe](https://andrewlock.net/making-getoradd-on-concurrentdictionary-thread-safe-using-lazy/). The job of actually creating the handler occurs in `CreateHandlerEntry` (which we'll see shortly) which creates an `ActiveHandlerTrackingEntry`.

The `ActiveHandlerTrackingEntry` is _mostly_ an immutable object containing an `HttpMessageHandler` and a DI `IServiceScope`. In reality it also contains an internal timer that is used with the `StartExpiryTimer()` method to call the provided `callback` when the timer of length `Lifetime` expires.

```csharp
internal class ActiveHandlerTrackingEntry
{
    public LifetimeTrackingHttpMessageHandler Handler { get; private set; }
    public TimeSpan Lifetime { get; }
    public string Name { get; }
    public IServiceScope Scope { get; }
    public void StartExpiryTimer(TimerCallback callback)
    {
        // Starts the internal timer
        // Executes the callback after Lifetime has expired.
        // If the timer has already started, is noop
    }
}
```

So the `CreateHandler` method either creates a new `ActiveHandlerTrackingEntry`, or retrieves the entry from the dictionary, and starts the timer. In the next section we'll look at how the `CreateHandlerEntry()` method creates the `ActiveHandlerTrackingEntry` instances:

## Creating and tracking HttpMessageHandlers in CreateHandlerEntry

The `CreateHandlerEntry` method is where the `HttpClient` handler pipelines are created. It's a somewhat complex method, so I'll show it first, and then talk through it afterwards. The version shown below is somewhat simplified [compared to the real version](https://github.com/dotnet/runtime/blob/065bf96e5c298352c790b9a82564d57a745725f5/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs#L151), but it maintains all the salient points.

```csharp
// The root service provider, injected into the constructor using DI
private readonly IServiceProvider _services;

// A collection of IHttpMessageHandler "configurers" that are added to every handler pipeline
private readonly IHttpMessageHandlerBuilderFilter[] _filters;

private ActiveHandlerTrackingEntry CreateHandlerEntry(string name)
{
    IServiceScope scope = _services.CreateScope(); 
    IServiceProvider services = scope.ServiceProvider;
    HttpClientFactoryOptions options = _optionsMonitor.Get(name);

    HttpMessageHandlerBuilder builder = services.GetRequiredService<HttpMessageHandlerBuilder>();
    builder.Name = name;

    // This is similar to the initialization pattern in:
    // https://github.com/aspnet/Hosting/blob/e892ed8bbdcd25a0dafc1850033398dc57f65fe1/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs#L188
    Action<HttpMessageHandlerBuilder> configure = Configure;
    for (int i = _filters.Length - 1; i >= 0; i--)
    {
        configure = _filters[i].Configure(configure);
    }

    configure(builder);

    // Wrap the handler so we can ensure the inner handler outlives the outer handler.
    var handler = new LifetimeTrackingHttpMessageHandler(builder.Build());

    return new ActiveHandlerTrackingEntry(name, handler, scope, options.HandlerLifetime);

    void Configure(HttpMessageHandlerBuilder b)
    {
        for (int i = 0; i < options.HttpMessageHandlerBuilderActions.Count; i++)
        {
            options.HttpMessageHandlerBuilderActions[i](b);
        }
    }
}
```

There's quite a lot to unpack here. The method starts by creating a new `IServiceScope` using the root DI container. This creates a DI scope, so that scoped services can be sourced from the associated `IServiceProvider`. We also retrieve the `HttpClientFactoryOptions` for the requested `HttpClient` name, which contains the specific handler configuration for this instance.

The next item retrieved from the container is an `HttpMessageHandlerBuilder`, which by default is [a `DefaultHttpMessageHandlerBuilder`](https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpMessageHandlerBuilder.cs). This is used to build the handler pipeline, by creating a "primary" handler, which is the `HttpClientHandler` that is responsible for making the socket connection and sending the request. You can add additional `DelegatingHandler`s that wrap the primary server, creating a pipeline for requests and responses.

![Image of the delegating handlers and primary handler](https://andrewlock.net/content/images/2020/httpclientfactory_01.png)

The `DelegatingHandler`s are added to the builder using a slightly complicated arrangement, that is very reminiscent of how the middleware pipeline in an ASP.NET Core app is built:

- The `Configure()` local method builds a pipeline of `DelegatingHandler`s, based on the configuration you provided in `Startup.ConfigureServices()`.
- `IHttpMessageHandlerBuilderFilter` are _filters_ that are injected into the `IHttpClientFactory` constructor. They are used to add _additional_ handlers into the `DelegatingHandler` pipeline. 

`IHttpMessageHandlerBuilderFilter` are directly analogous to the `IStartupFilter`s used by the ASP.NET Core middleware pipeline, [which I've talked about previously](https://andrewlock.net/exploring-istartupfilter-in-asp-net-core/). A single `IHttpMessageHandlerBuilderFilter` is registered by default, the `LoggingHttpMessageHandlerBuilderFilter`. [This filter](https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/Microsoft.Extensions.Http/src/Logging/LoggingHttpMessageHandlerBuilderFilter.cs) adds two additional handlers to the `DelegatingHandler` pipeline:

- `LoggingScopeHttpMessageHandler` at the start (outside) of the pipeline, which starts a new [logging scope](https://docs.microsoft.com/aspnet/core/fundamentals/logging/#log-scopes).
- A `LoggingHttpMessageHandler` at the end (inside) of the pipeline, just before the request is sent to the primary `HttpClientHandler`, which records logs about the request and response.

Finally, the whole pipeline is wrapped in [a `LifetimeTrackingHttpMessageHandler`, which is a "noop" `DelegatingHandler`](https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/Microsoft.Extensions.Http/src/LifetimeTrackingHttpMessageHandler.cs). We'll come back to the purpose of this handler later.

The code (and probably my description) for `CreateHandlerEntry` is definitely somewhat hard to follow, but the end result for an `HttpClient` configured with two custom handlers (as demonstrated at the start of this post) is a handler pipeline that looks something like the following:

![The final build handler pipeline](https://andrewlock.net/content/images/2020/httpclientfactory_02.png)

Once the handler pipeline is complete, it is saved in a new `ActiveHandlerTrackingEntry` instance, along with the DI `IServiceScope` used to create it, and given the lifetime defined in `HttpClientFactoryOptions` (two minutes by default).

This entry is returned to the caller (the `CreateHandler()` method), added to the `ConcurrentDictionary<>` of handlers, added to a new `HttpClient` instance (in the `CreateClient()` method), and returned to the original caller.

For the next two minutes, every time you call `CreateClient()`, you will get a _new_ instance of `HttpClient`, but which has the _same_ handler pipeline as was originally created.

> Each _named_ or _typed_ client gets its own message handler pipeline. i.e. two instances of the `"github"` named client will have the same handler chain, but the `"api"` named client would have a different handler chain.

The next challenge is cleaning up and disposing the handler chain once the two minute timer has expired. This requires some careful handling, as you'll see in the next section.

## Cleaning up expired handlers.

After two minutes, the timer stored in the `ActiveHandlerTrackingEntry` entry will expire, and fire the `callback` method passed into `StartExpiryTimer()`: `ExpiryTimer_Tick()`.

`ExpiryTimer_Tick` is responsible for removing the active handler entry from the `ConcurrentDictionary<>` pool, and adding it to a queue of expired handlers:

```csharp
// Created in the constructor
readonly ConcurrentQueue<ExpiredHandlerTrackingEntry> _expiredHandlers;

// The Timer instance in ActiveHandlerTrackingEntry calls this when it expires
internal void ExpiryTimer_Tick(object state)
{
    var active = (ActiveHandlerTrackingEntry)state;

     _activeHandlers.TryRemove(active.Name, out Lazy<ActiveHandlerTrackingEntry> found);

    var expired = new ExpiredHandlerTrackingEntry(active);
    _expiredHandlers.Enqueue(expired);

    StartCleanupTimer();
}
```

Once the `active` handler is removed from the `_activeHandlers` collection, it will no longer be handed out with new `HttpClient`s when your call `CreateClient()`. But there are potentially `HttpClient`s out there which are still using the `active` handler. The `IHttpClientFactory` has to wait for all the `HttpClient` instances that reference this handler to be cleaned up before it can dispose the handler pipeline.

The problem is, how can `IHttpClientFactory` track that the handler is no longer referenced? The key is the use of the "noop" `LifetimeTrackingHttpMessageHandler`, and the `ExpiredHandlerTrackingEntry`.

The `ExpiredHandlerTrackingEntry`, shown below, [creates a `WeakReference`](https://docs.microsoft.com/dotnet/api/system.weakreference) to the `LifetimeTrackingHttpMessageHandler`. As I showed in the previous section, the `LifetimeTrackingHttpMessageHandler` is the "outermost" handler in the chain, so it is the handler that is _directly_ referenced by the `HttpClient`.

```csharp
internal class ExpiredHandlerTrackingEntry
{
    private readonly WeakReference _livenessTracker;

    // IMPORTANT: don't cache a reference to `other` or `other.Handler` here.
    // We need to allow it to be GC'ed.
    public ExpiredHandlerTrackingEntry(ActiveHandlerTrackingEntry other)
    {
        Name = other.Name;
        Scope = other.Scope;

        _livenessTracker = new WeakReference(other.Handler);
        InnerHandler = other.Handler.InnerHandler;
    }

    public bool CanDispose => !_livenessTracker.IsAlive;

    public HttpMessageHandler InnerHandler { get; }
    public string Name { get; }
    public IServiceScope Scope { get; }
}
```

Using `WeakReference` to the `LifetimeTrackingHttpMessageHandler` means that the _only direct references_ to the outermost handler in the chain are in `HttpClient`s. Once all these `HttpClient`s have been collected by the garbage collector, the `LifetimeTrackingHttpMessageHandler` will have no references, and so will _also_ be disposed. The `ExpiredHandlerTrackingEntry` can detect that via the `WeakReference.IsAlive` property.

> Note that the `ExpiredHandlerTrackingEntry` _does_ maintain a reference to the rest of the handler pipeline, so that it can properly dispose the inner handler chain, as well as the DI `IServiceScope`.

After an entry is added to the `_expiredHandlers` queue, a timer is started by `StartCleanupTimer()` which fires after 10 seconds. This calls the `CleanupTimer_Tick()` method, which checks to see whether all the references to the handler have expired. If so, the handler chain and `IServiceScope` are disposed. If not, they are added back onto the queue, and the clean-up timer is started again:

```csharp
internal void CleanupTimer_Tick()
{
    // Stop any pending timers, we'll restart the timer if there's anything left to process after cleanup.
    StopCleanupTimer();

    // Loop through all the timers
    int initialCount = _expiredHandlers.Count;
    for (int i = 0; i < initialCount; i++)
    {
        _expiredHandlers.TryDequeue(out ExpiredHandlerTrackingEntry entry);

        if (entry.CanDispose)
        {
            // All references to the handler chain have been removed
            try
            {
                entry.InnerHandler.Dispose();
                entry.Scope?.Dispose();
            }
            catch (Exception ex)
            {
                // log the exception
            }
        }
        else
        {
            // If the entry is still live, put it back in the queue so we can process it
            // during the next cleanup cycle.
            _expiredHandlers.Enqueue(entry);
        }
    }

    // We didn't totally empty the cleanup queue, try again later.
    if (_expiredHandlers.Count > 0)
    {
        StartCleanupTimer();
    }
}
```

The method presented above is pretty simple - it loops through each of the `ExpiredHandlerTrackingEntry` entries in the queue, and checks if all references to the `LifetimeTrackingHttpMessageHandler` handlers have been removed. If they have, the handler chain (everything that was _inside_ the lifetime tracking handler) is disposed, as is the DI `IServiceScope`.

If there are still live references to any of the `LifetimeTrackingHttpMessageHandler` handlers, the entries are put back in the queue, and the cleanup timer is started again. Every 10 seconds another cleanup sweep is run.

> I simplified the `CleanupTimer_Tick()` method shown above [compared to the original](https://github.com/dotnet/runtime/blob/065bf96e5c298352c790b9a82564d57a745725f5/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs#L260). The original adds additional logging, and uses locking to ensure only a single thread runs the cleanup at a time.

That brings us to the end of this deep dive in the internals of `IHttpClientFactory`! `IHttpClientFactory` shows the _correct_ way to manage `HttpClient` and `HttpMessageHandler`s in your application. Having read through the code, it's understandable that noone got this completely right for so long - there's a lot of tricky gotchas there! If you've made it this far, I suggest going and [looking through the original code](https://github.com/dotnet/runtime/blob/065bf96e5c298352c790b9a82564d57a745725f5/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs), I highlighted (what I consider) the most important points in this post, but you can learn a lot from reading other people's code!

## Summary

In this post I looked at the source code behind the default `IHttpClientFactory` implementation in .NET Core 3.1, `DefaultHttpClientFactory`. I showed how the factory stores an active `HttpMessageHandler` pipeline for each configured named or typed client, with each new `HttpClient` getting a reference to the same pipeline. I showed that the handler pipeline is built in a similar way to the ASP.NET Core middleware pipeline, using handlers. Finally, I showed how the factory tracks whether any `HttpClient`s reference a pipeline instance by using a `WeakReference` to the handler.