DEV Community

Cover image for Your custom HttpClient delegating handlers should be transient
Anthony Simmon
Anthony Simmon

Posted on • Originally published at anthonysimmon.com

1

Your custom HttpClient delegating handlers should be transient

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 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:

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

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

services.AddHttpClient("MyClient")
    .AddHttpMessageHandler<MyCustomDelegatingHandler>();
Enter fullscreen mode Exit fullscreen mode

This code is also susceptible to the error mentioned above:

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

services.AddHttpClient("MyClient")
    .AddHttpMessageHandler(() => handler);
Enter fullscreen mode Exit fullscreen mode

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

HttpClient handler pipeline

Different DelegatingHandlers are linked to each other through the InnerHandler property, thanks to the CreateHandlerPipeline 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. By default, this duration is 2 minutes. During this time, as many HttpClients 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. 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 HttpClients, 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.

// 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());
Enter fullscreen mode Exit fullscreen mode

Image of Datadog

The Future of AI, LLMs, and Observability on Google Cloud

Datadog sat down with Google’s Director of AI to discuss the current and future states of AI, ML, and LLMs on Google Cloud. Discover 7 key insights for technical leaders, covering everything from upskilling teams to observability best practices

Learn More

Top comments (1)

Collapse
 
artydev profile image
artydev

Thank you

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay