Aspnetcore: Using AddTypedClient throws "The HttpClient factory already has a registered client with the name" error

Created on 22 Aug 2019  路  6Comments  路  Source: dotnet/aspnetcore

Edit by @rynowak See below for shiproom template

Describe the bug

Trying to register a Http Client service using AddTypedClient throws "The HttpClient factory already has a registered client with the name" on first request.

To Reproduce

Steps to reproduce the behavior:

  1. Create a new ASP.NET Core Web Application -> v3.0 API project using preview 8 and Visual Studio 16.3.0 Preview 2.0
  2. Add the following in ConfigureServices
    services.AddHttpClient<IFoo>().AddTypedClient((hc, sp) => { return new Foo(); });
  3. Add the following class in the same namespace
public interface IFoo
{
}

public class Foo : IFoo
{
}
  1. Press F5

Expected behavior

The api is hit and result returned

Actual behaviour

The following error occurs:
The HttpClient factory already has a registered client with the name 'IFoo', bound to the type 'WebApplication2.IFoo'. Client names are computed based on the type name without considering the namespace ('IFoo'). Use an overload of AddHttpClient that accepts a string and provide a unique name to resolve the conflict.
at Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.ReserveClient(IHttpClientBuilder builder, Type type, String name) at Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddTypedClient[TClient](IHttpClientBuilder builder, Func``3 factory) at WebApplication2.Startup.ConfigureServices(IServiceCollection services) in C:\Users\swanickm\source\repos\WebApplication2\WebApplication2\Startup.cs:line 29 at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.InvokeCore(Object instance, IServiceCollection services) at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.<>c__DisplayClass9_0.<Invoke>g__Startup|0(IServiceCollection serviceCollection) at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.Invoke(Object instance, IServiceCollection services) at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.<>c__DisplayClass8_0.<Build>b__0(IServiceCollection services) at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services) at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass12_0.<UseStartup>b__0(HostBuilderContext context, IServiceCollection services) at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider() at Microsoft.Extensions.Hosting.HostBuilder.Build() at WebApplication2.Program.Main(String[] args) in

Additional context

This works perfectly fine when using the ASP.NET Core 2.2 API template

Fixed area-httpclientfactory bug

Most helpful comment

The intent here was to help people catch mistakes in some invalid cases. It looks like this is blocking some legitimate usage, and we'll fix it.

All 6 comments

Hi, I'm encountering the same issue with the usage of Refit. following the official documentation : https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.0#generated-clients . Any update on this issue ?

NB: this is my sample implementation :

services.AddHttpClient('myclient', client =>
{
    client.BaseAddress = new Uri(endpoint);
    client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
})
.AddTypedClient(httpClient => Refit.RestService.For<ILocalitiesService>(httpClient)) // throw here
.AddTypedClient(httpClient => Refit.RestService.For<IPoisService>(httpClient))
.AddTypedClient(httpClient => Refit.RestService.For<IPolygonsService>(httpClient))

After digging into the problem, I've found that this is a "desired regression" introduced with this PR . You can follow this issue or this Feature Request to find a potential update on it.

Until a solution is found, I will just copy-paste the function without the call to ReserveClient like this :
````csharp
public static IHttpClientBuilder AddTypedClient(this IHttpClientBuilder builder, Func factory)
where TClient : class
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}

        if (factory == null)
        {
            throw new ArgumentNullException(nameof(factory));
        }

        // ReserveClient(builder, typeof(TClient), builder.Name);

        builder.Services.AddTransient<TClient>(s =>
        {
            var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
            var httpClient = httpClientFactory.CreateClient(builder.Name);

            return factory(httpClient);
        });

        return builder;
    }

```

The intent here was to help people catch mistakes in some invalid cases. It looks like this is blocking some legitimate usage, and we'll fix it.

I've just faced this issue and here's my nasty workaround using reflection.

internal static void RemoveHttpClient<T>(this IServiceCollection services)
{
    var registryType = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
        .SingleOrDefault(t => t.Name == "HttpClientMappingRegistry");
    var registry = services.SingleOrDefault(s => s.ServiceType == registryType)?.ImplementationInstance;
    var registrations = registry?.GetType().GetProperty("TypedClientRegistrations");
    var clientRegistrations = registrations?.GetValue(registry) as IDictionary<Type, string>;
    clientRegistrations?.Remove(typeof(T));
}

Didn't work to me. Thus I hold any migration to 3.0 until the bug gets addressed.

Description

In 3.0 we added validation to HttpClient Factory's registration code path to prevent some common mistakes. This new validation code blocks some valid use cases that we didn't know about.

Customer Impact

Attempting to call AddTypedClient<T> twice on the same builder with different types throws an exception. This is a valuable shorthand way of expressing a configuration once and then binding it to multiple types.

Regression?

Yes, this is a regression from 2.2

Risk

Low. This add special casing to our validation code path to allow more patterns. The impact of this is that cases that are explicitly blocked by an exception in 3.0 (but were allowed in 2.2) will be allowed again.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mj1856 picture mj1856  路  3Comments

rbanks54 picture rbanks54  路  3Comments

githubgitgit picture githubgitgit  路  3Comments

FourLeafClover picture FourLeafClover  路  3Comments

Kevenvz picture Kevenvz  路  3Comments