提问人:Aj Hadi 提问时间:10/9/2022 最后编辑:David LiangAj Hadi 更新时间:11/17/2023 访问量:411
添加具有相同接口的 HttpClients 最终具有相同的基本 url Asp.Net Core
Add HttpClients with same Interface ended up having the same base url Asp.Net Core
问:
我在 ASP.Net Core 中有一个程序。我在 上添加了两个具有相同接口的类:HttpClient
Startup.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填充了每个客户端。HttpClient
BaseAddress
public class SomeController : Controller
{
private readonly IEnumerable<ITypedClient> _typedClients;
public OtherService(IEnumerable<ITypedClient> typedClients)
{
_typedClients = typedClients;
}
}
但是,如果我没有接口:AddHttpClient
Startup.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;
}
}
在每个类型化的客户端上都有自己的 .HttpClient
BaseAddress
我做错了什么?我希望每个类型化客户端都具有相同的接口,但我也希望其中的每个实例都具有不同的 .有什么解决方案吗?HttpClient
BaseAddress
答:
.NET 依赖项注入容器的工作方式是,当您为同一接口注册多个依赖项时,所有这些依赖项都将被注册,并且可以通过 解析。因此,您的第一种方法在您的情况下行不通。你的第二种方法很好。我看不出有任何理由将它们都明确注册到您的界面中。IEnumerable<IInterface>
简短摘要
每个类型化客户端中实例化的都是根据接口的名称创建的,如果你只是这样做的话。因此,是相同的实例。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”。HttpClient
TypedClientA
TypedClientB
BaseAddress
演示: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.GetTypeDisplayName
fullName
false
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 只会给你 .全名将为您提供接口的名称以及命名空间。ITypedClient
typeof(ITypedClient).Name
"ITypedClient"
从接口获取名称后,该名称将传递给 。它只是将其属性设置为您传入的任何内容。源代码在这里。DefaultHttpClientBuilder
Name
接下来,在(源代码)上,使用名称: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
因此,如果有多个类型化客户端实现相同的接口,则在每个类型化客户端中将获得相同的实例!这就是为什么您将始终从您的初创公司获得最后的设置,例如 、 等。HttpClient
HttpClient
BaseAddress
DefaultRequestHeaders
溶液
我希望每个类型化客户端都具有相同的接口,但我也希望其中的每个 HttpClient 实例具有不同的 BaseAddress。有什么解决方案吗?
有一个覆盖,因为它有一个名字。只要您为其提供唯一的名称,每个类型化客户端中的每个实例都将是其自己的实例!.AddHttpClient<>()
HttpClient
services.AddHttpClient<ITypedClient, TypedClientA>($"{nameof(TypedClientA)}HttpClient",
httpClient => ..);
services.AddHttpClient<ITypedClient, TypedClientB>($"{nameof(TypedClientB)}HttpClient",
httpClient => ...);
我只是选择该名称作为类型化客户端实现的名称,后跟“HttpClient”文本,但您可以自由使用所需的任何唯一名称。
评论
Class
Interface