添加具有相同接口的 HttpClients 最终具有相同的基本 url Asp.Net Core

Add HttpClients with same Interface ended up having the same base url Asp.Net Core

提问人:Aj Hadi 提问时间:10/9/2022 最后编辑:David LiangAj Hadi 更新时间:11/17/2023 访问量:411

问:

我在 ASP.Net Core 中有一个程序。我在 上添加了两个具有相同接口的类:HttpClientStartup.cs

public class TypedClientA : ITypedClient
{
    private readonly HttpClient _httpClient;

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

public class TypedClientB : ITypedClient
{
    private readonly HttpClient _httpClient;

    public class TypedClientB(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
}

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddHttpClient<ITypedClient, TypedClientA>(httpClient =>
    {
        httpClient.BaseAddress = new Uri("uri class A");
    });

    services.AddHttpClient<ITypedClient, TypedClientB>(httpClient =>
    {
        httpClient.BaseAddress = new Uri("uri class B");
    });
    ...
}

然后我尝试在我的控制器中调用它们,但是每个类型化客户端中的实例始终具有相同的,即使我在启动期间用不同的基URL填充了每个客户端。HttpClientBaseAddress

public class SomeController : Controller
{
    private readonly IEnumerable<ITypedClient> _typedClients;

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

但是,如果我没有接口:AddHttpClientStartup.cs

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddHttpClient<TypedClientA>(httpClient =>
    {
        httpClient.BaseAddress = new Uri("uri class A");
    });

    services.AddHttpClient<TypedClientB>(httpClient =>
    {
        httpClient.BaseAddress = new Uri("uri class B");
    });
    ...
}
public class SomeController : Controller
{
   private readonly TypedClientA _typedClientA;
   private readonly TypedClientB _typedClientB;

   public Class(TypedClientA typedClientA, TypedClientB typedClientB)
   {
      _typedClientA = typedClientA;
      _typedClientB = typedClientB;
   }
}

在每个类型化的客户端上都有自己的 .HttpClientBaseAddress

我做错了什么?我希望每个类型化客户端都具有相同的接口,但我也希望其中的每个实例都具有不同的 .有什么解决方案吗?HttpClientBaseAddress

C# asp.net asp.net-core dotnet-httpclient

评论

0赞 Peter Csala 10/11/2022
在你的 是否要访问实现的两个类或仅访问其中一个类?ClassInterface
0赞 qwermike 5/19/2023
这回答了你的问题吗?HttpClient 的多个实现

答:

1赞 T. van Schagen 10/10/2022 #1

.NET 依赖项注入容器的工作方式是,当您为同一接口注册多个依赖项时,所有这些依赖项都将被注册,并且可以通过 解析。因此,您的第一种方法在您的情况下行不通。你的第二种方法很好。我看不出有任何理由将它们都明确注册到您的界面中。IEnumerable<IInterface>

1赞 David Liang 11/16/2023 #2

简短摘要

每个类型化客户端中实例化的都是根据接口的名称创建的,如果你只是这样做的话。因此,是相同的实例。HttpClient.AddHttpClient<ITypedClient, TypedClientImplementation>()HttpClient

如果启动时有多个配置,则以最后一个配置为准。例如.AddHttpClient()

services.AddHttpClient<ITypedClient, TypedClientA>(httpClient =>
{
    httpClient.BaseAddress = new Uri("address1");
});

services.AddHttpClient<ITypedClient, TypedClientB>(httpClient =>
{
    httpClient.BaseAddress = new Uri("address2");
});

两个 和 中的实例是同一个实例,但其是“地址 2”。HttpClientTypedClientATypedClientBBaseAddress

演示:https://dotnetfiddle.net/ldryCt

长篇解释

如果您查看采用的扩展方法的源代码和配置 Http 客户端的操作,您会注意到它试图从中获取名称:.AddHttpClient()<TClient, TImplementaiton>typeof(TClient)

public static IHttpClientBuilder AddHttpClient<TClient, TImplementation>(
    this IServiceCollection services, Action<HttpClient> configureClient)
    where TClient : class
    where TImplementation : class, TClient
{
    ...
    AddHttpClient(services);

    string name = TypeNameHelper.GetTypeDisplayName(typeof(TClient), fullName: false);
    var builder = new DefaultHttpClientBuilder(services, name);
    builder.ConfigureHttpClient(configureClient);
    builder.AddTypedClientCore<TClient, TImplementation>(validateSingleType: true);
    ...
}

如果您继续寻找 的源代码,您可以看到 name 是您的接口名称,因为它传递为 :TypeNameHelper.GetTypeDisplayNamefullNamefalse

public static string GetTypeDisplayName(Type type, bool fullName = true, ...)
{
    var builder = new StringBuilder();
    ProcessType(builder, type, new DisplayNameOptions(fullName, ...));
    return builder.ToString();
}

private static void ProcessType(StringBuilder builder, Type type, in DisplayNameOptions options)
{
    if (type.IsGenericType)
        ...
    else
    {
        var name = options.FullName? type.FullName! : type.Name;
        ...
    }
}

话虽如此,例如,如果你的接口被调用,则 doing 只会给你 .全名将为您提供接口的名称以及命名空间。ITypedClienttypeof(ITypedClient).Name"ITypedClient"

从接口获取名称后,该名称将传递给 。它只是将其属性设置为您传入的任何内容。源代码在这里。DefaultHttpClientBuilderName

接下来,在(源代码)上,使用名称:builder.ConfigureHttpClient(configureClient)

public static IHttpClientBuilder ConfigureHttpClient(this IHttpClientBuilder builder, 
    Action<HttpClient> configureClient)
{
    ...
    builder.Services.Configure<HttpClientFactoryOptions>(builder.Name,
        options => options.HttpClientActions.Add(configureClient));
    ...
}

您可以看到 继续用作客户端工厂选项实例。name

接下来,如果我们看一下(源代码),有一个叫做 call 的方法:builder.AddTypedClientCore<TClient, TImplementation>(true)AddTransientHelper<TClient>()

internal static IHttpBuilder AddTypedClientCore<TClient>(this IHttpClientBuilder builder,
    bool validateSingleType)
    where TClient : class
{
    ...
    builder.Services.AddTransient(s => AddTransientHelper<TClient>(s, builder));
    ...
}

private static TClient AddTransientHelper<TClient>(IServiceProvider s, 
    IHttpClientBuilder builder)
    where TClient : class
{
    ...
    HttpClient httpClient = httpClientFactory.CreateClient(builder.Name);
    ...
}

现在,您可以看到每个类型化客户端中的实例化都是基于接口的名称!HttpClient

因此,如果有多个类型化客户端实现相同的接口,则在每个类型化客户端中将获得相同的实例!这就是为什么您将始终从您的初创公司获得最后的设置,例如 、 等。HttpClientHttpClientBaseAddressDefaultRequestHeaders

溶液

我希望每个类型化客户端都具有相同的接口,但我也希望其中的每个 HttpClient 实例具有不同的 BaseAddress。有什么解决方案吗?

有一个覆盖,因为它有一个名字。只要您为其提供唯一的名称,每个类型化客户端中的每个实例都将是其自己的实例!.AddHttpClient<>()HttpClient

services.AddHttpClient<ITypedClient, TypedClientA>($"{nameof(TypedClientA)}HttpClient", 
    httpClient => ..);
services.AddHttpClient<ITypedClient, TypedClientB>($"{nameof(TypedClientB)}HttpClient", 
    httpClient => ...);

我只是选择该名称作为类型化客户端实现的名称,后跟“HttpClient”文本,但您可以自由使用所需的任何唯一名称。

演示:https://dotnetfiddle.net/4At6V2