Your custom HttpClient delegating handlers should be transient
Tags: dotnet, csharp, programming
Published: 2024-04-05 13:30:00

If you've ever encountered the following error, this article is for you:

> _InvalidOperationException: The 'InnerHandler' property must be null. 'DelegatingHandler' instances provided to 'HttpMessageHandlerBuilder' must not be reused or cached._

This means you are using the [Microsoft.Extensions.Http](https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory) library and have modified an `HttpClient`'s handler pipeline by inserting a `DelegatingHandler`. As the error indicates, you cannot reuse a handler across multiple `HttpClient` instances. In other words, your `DelegatingHandler` **cannot be a singleton**. Here's an example of problematic code:

```csharp
internal sealed class MyCustomDelegatingHandler : DelegatingHandler
{
    // Implementation omitted
}

// Registering the handler as a singleton is the mistake
services.AddSingleton<MyCustomDelegatingHandler>();

services.AddHttpClient("MyClient")
    .AddHttpMessageHandler<MyCustomDelegatingHandler>();
```

This code is also susceptible to the error mentioned above:

```csharp
// This same instance will be used for all named HttpClient "MyClient"
var handler = new MyCustomDelegatingHandler();

services.AddHttpClient("MyClient")
    .AddHttpMessageHandler(() => handler);
```

Let's try to understand what's wrong here. The implementation of `IHttpClientFactory`, the service producing `HttpClient` instances, constructs the pipeline from multiple `DelegatingHandler`s making up an `HttpClient`. Each time an `HttpClient` is requested, the pipeline *may* be recreated.

![HttpClient handler pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfzeyzsiux0ksuow1jao.png)

Different `DelegatingHandler`s are linked to each other through the `InnerHandler` property, thanks to the [CreateHandlerPipeline](https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/Microsoft.Extensions.Http/src/HttpMessageHandlerBuilder.cs#L81) method. This method checks that the `InnerHandler` property isn't already set and throws the `InvalidOperationException` mentioned above if it is.

Handlers created as part of the `IHttpClientFactory` implementation are temporarily stored in a cache, for a duration equal to [HttpClientFactoryOptions.HandlerLifetime](https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/Microsoft.Extensions.Http/src/HttpClientFactoryOptions.cs#L58). By default, **this duration is 2 minutes**. During this time, as many `HttpClient`s as needed can be created and used without issue.

However, once the handler is removed from the cache, it will go through the `CreateHandlerPipeline` method again, and if it is a singleton, the `InnerHandler` property will already be set, leading to the exception being thrown.

If you control the lifecycle of the `HttpClient` and only create one throughout the lifespan of your application, you might never notice this issue. But often, developers overlook this aspect and are not aware of the exact context in which their `HttpClient` is used.

A particularly noticeable and error-prone example is that of [typed clients](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#how-to-use-typed-clients-with-ihttpclientfactory). Typed clients are registered using the generic `AddHttpClient<TClient>` method (and its overloads), but in my experience, few developers realize that the `TClient` type becomes registered in services with a **transient lifecycle**. Thus, requesting this `TClient` multiple times will result in the creation of multiple `HttpClient`s, causing the pipeline construction error if one of your delegating handlers is a singleton.

The solution is simple: ensure that the handler factory passed to the `AddHttpMessageHandler` method always returns a new instance.

```csharp
// Registering the handler as a transient is the fix
services.AddTransient<MyCustomDelegatingHandler>();

services.AddHttpClient("MyClient")
    .AddHttpMessageHandler<MyCustomDelegatingHandler>();

// Or explicitly create a new instance each time
services.AddHttpClient("MyClient")
    .AddHttpMessageHandler(() => new MyCustomDelegatingHandler());
```