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>();
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);
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.
Different DelegatingHandler
s 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 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. 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.
// 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());