Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding multiple HttpClients with same Interface results in conflicting configurations #110996

Open
stormaref opened this issue Dec 30, 2024 · 5 comments · May be fixed by #110999
Open

Adding multiple HttpClients with same Interface results in conflicting configurations #110996

stormaref opened this issue Dec 30, 2024 · 5 comments · May be fixed by #110999
Labels
area-Extensions-HttpClientFactory in-pr There is an active PR which will close this issue when it is merged untriaged New issue has not been triaged by the area owner

Comments

@stormaref
Copy link

Description

When registering multiple HttpClient services using HttpClientFactoryServiceCollectionExtensions.AddHttpClient with the same interface, the resulting services share the same configuration (e.g., BaseAddress). This behavior leads to unexpected results when injecting these services as an IEnumerable into a controller or service.

Reproduction Steps

  1. Define an interface and two typed clients that implement it:
   public interface ITypedClient { }

   public class TypedClientA : ITypedClient
   {
       private readonly HttpClient _httpClient;

       public TypedClientA(HttpClient httpClient)
       {
           _httpClient = httpClient;
       }
   }

   public class TypedClientB : ITypedClient
   {
       private readonly HttpClient _httpClient;

       public TypedClientB(HttpClient httpClient)
       {
           _httpClient = httpClient;
       }
   }
  1. Register the services in Program.cs:
builder.Services.AddHttpClient<ITypedClient, TypedClientA>(httpClient =>
{
    httpClient.BaseAddress = new Uri("https://exampleA.com");
});

builder.Services.AddHttpClient<ITypedClient, TypedClientB>(httpClient =>
{
    httpClient.BaseAddress = new Uri("https://exampleB.com");
});
  1. Inject the services and observe their BaseAddress:
public class SomeController : Controller
{
    private readonly IEnumerable<ITypedClient> _typedClients;

    public SomeController(IEnumerable<ITypedClient> typedClients)
    {
        _typedClients = typedClients;
    }
}

Expected behavior

When multiple HttpClient instances are registered using the same interface with AddHttpClient, each implementation should retain its unique configuration. Specifically:

  • TypedClientA should be configured with a BaseAddress of https://exampleA.com.
  • TypedClientB should be configured with a BaseAddress of https://exampleB.com.

When these services are injected as IEnumerable<ITypedClient> into a controller or service, the collection should contain both implementations, each retaining its respective configuration. For example:

  1. TypedClientA should make HTTP requests to https://exampleA.com.
  2. TypedClientB should make HTTP requests to https://exampleB.com.

The system should correctly distinguish between the two implementations without overwriting or merging their configurations.

Actual behavior

When multiple HttpClient instances are registered using the same interface with AddHttpClient, the configurations for the services are overwritten. Specifically:

  • Both TypedClientA and TypedClientB end up sharing the same BaseAddress, which is determined by the last registered configuration.

For example:

  • If TypedClientB is registered after TypedClientA, both clients will have the BaseAddress set to https://exampleB.com.
  • If the order of registration is reversed, both clients will have the BaseAddress set to https://exampleA.com.

When these services are injected as IEnumerable<ITypedClient> into a controller or service, both implementations refer to the same HttpClient configuration, resulting in unexpected and incorrect behavior.

Regression?

This issue has been present since at least .NET 7 and continues to occur in .NET 8 and .NET 9. The behavior where multiple HttpClient instances with the same interface overwrite each other's configurations has persisted across these versions.

Testing with previous versions of .NET (7, 8, and 9) confirms that this issue has been ongoing, suggesting it may be a long-standing bug or unintended behavior in the framework's AddHttpClient method.

Known Workarounds

No response

Configuration

No response

Other information

No response

@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Dec 30, 2024
@KalleOlaviNiemitalo
Copy link

It looks like IHttpClientBuilder.Name is "ITypedClient" for both TypedClientA and TypedClientB, and that is then used as ConfigureNamedOptions<HttpClientFactoryOptions>.Name, so both callbacks are added to the same HttpClientFactoryOptions.HttpClientActions list.

A workaround might be:

builder.Services.AddHttpClient<ITypedClient, TypedClientB>(httpClient =>
{
    if (httpClient is TypedClientB)
    {
        httpClient.BaseAddress = new Uri("https://exampleB.com");
    }
});

but this wouldn't help if you wanted two differently-configured instances of the same implementation type TypedClientB.

@stormaref
Copy link
Author

It looks like IHttpClientBuilder.Name is "ITypedClient" for both TypedClientA and TypedClientB, and that is then used as ConfigureNamedOptions.Name, so both callbacks are added to the same HttpClientFactoryOptions.HttpClientActions list.

A workaround might be:

builder.Services.AddHttpClient<ITypedClient, TypedClientB>(httpClient =>
{
if (httpClient is TypedClientB)
{
httpClient.BaseAddress = new Uri("https://exampleB.com");
}
});
but this wouldn't help if you wanted two differently-configured instances of the same implementation type TypedClientB.

This is not a fix

@KalleOlaviNiemitalo
Copy link

Right, it was for the "Known Workarounds" section.

stormaref pushed a commit to stormaref/runtime that referenced this issue Dec 30, 2024
Changed DefaultHttpClientBuilder name and added new test

Fix dotnet#110996
@stormaref stormaref linked a pull request Dec 30, 2024 that will close this issue
@dotnet-policy-service dotnet-policy-service bot added the in-pr There is an active PR which will close this issue when it is merged label Dec 30, 2024
@ArminShoeibi
Copy link
Contributor

Maybe you should use this

#80202 (comment)

@stormaref
Copy link
Author

Maybe you should use this

#80202 (comment)

Thank you for your suggestion, but I want to clarify that I’m not looking for a workaround for this issue. My goal is to address and fix it for good. While there are alternative ways to handle this scenario, the current behavior is counterintuitive and inconsistent with the expected functionality of dependency injection. This is something that should work out of the box without requiring extra workarounds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-Extensions-HttpClientFactory in-pr There is an active PR which will close this issue when it is merged untriaged New issue has not been triaged by the area owner
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants