admin管理员组文章数量:1033955
基于 keyed DI 的 HttpClient
基于 keyed DI 的 HttpClient
Intro
.NET 8 中依赖注入引入了 keyed service 的支持 可以参考 .NET 8 中的 KeyedService,.NET 9 中改进了 HttpClient 基于名称的 HttpClient 依赖注入,使用基于名称的 HttpClient 的时候可以直接使用 keyed service 来解析了
Sample
我们可以在 AddHttpClient
之后使用 AddAsKeyed()
方法来注册 keyed service
使用示例如下:
代码语言:javascript代码运行次数:0运行复制var services = new ServiceCollection();
services.AddHttpClient("test1", client =>
{
client.BaseAddress = new Uri("http://localhost:6000");
})
.AddAsKeyed()
;
await using var provider = services.BuildServiceProvider();
var client1 = provider.GetRequiredKeyedService<HttpClient>("test1");
Console.WriteLine(client1.BaseAddress);
注册之后我们就可以从依赖注入容器根据名字获取 HttpClient 服务了如 provider.GetRequiredKeyedService<HttpClient>("test1")
而在之前我们需要使用
scope.ServiceProvider.GetRequiredService<IHttpClientFactory>()
.CreateClient("test1")
使用 keyed service 之后就可以简化一些了,在 asp core 还可以在 API action 方法上使用 [FromKeyedService("test1")HttpClient client]
的方式来使用
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "dotnet");
})
.AddAsKeyed(); // Add HttpClient as a Keyed Scoped service for key="github"
var app = builder.Build();
// Directly inject the Keyed HttpClient by its name
app.MapGet("/", ([FromKeyedServices("github")] HttpClient httpClient) =>
httpClient.GetFromJsonAsync<Repo>("/repos/dotnet/runtime"));
app.Run();
record Repo(string Name, string Url);
默认注册的 HttpClient
服务声明周期为 Scoped
,如果要调整可以
public static IHttpClientBuilder AddAsKeyed(this IHttpClientBuilder builder,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
示例代码如下:
代码语言:javascript代码运行次数:0运行复制var services = new ServiceCollection();
services.AddHttpClient("test1", client =>
{
client.BaseAddress = new Uri("http://localhost:5000");
})
.AddAsKeyed()
;
services.AddHttpClient("test2", client =>
{
client.BaseAddress = new Uri("http://localhost:6000");
})
.AddAsKeyed(ServiceLifetime.Singleton)
;
awaitusingvar provider = services.BuildServiceProvider();
{
awaitusingvar scope = provider.CreateAsyncScope();
var client1 = scope.ServiceProvider.GetRequiredKeyedService<HttpClient>("test1");
Console.WriteLine(client1.GetHashCode());
Console.WriteLine(client1.BaseAddress);
var client2 = scope.ServiceProvider.GetRequiredKeyedService<HttpClient>("test2");
Console.WriteLine(client2.GetHashCode());
Console.WriteLine(client2.BaseAddress);
}
{
awaitusingvar scope = provider.CreateAsyncScope();
var client1 = scope.ServiceProvider.GetRequiredKeyedService<HttpClient>("test1");
Console.WriteLine(client1.GetHashCode());
Console.WriteLine(client1.BaseAddress);
var client2 = scope.ServiceProvider.GetRequiredKeyedService<HttpClient>("test2");
Console.WriteLine(client2.GetHashCode());
Console.WriteLine(client2.BaseAddress);
}
这里这两个 HttpClient 一个是默认的生命周期一个是指定的 Singleton
,然后创建了两个 scope 输出,两个 scope 的 HttpClient 应该不相同,Singleton 则应该相同
输出结果如下:
如果有很多个 HttpClient 注册,为每个 HttpClient 都写一遍 AddAsKey()
也挺繁琐的,我们可以使用 .NET 8 中引入的 ConfigureHttpClientDefaults
来将所有的 HttpClient 都注册为 named HttpClient
就无需每个 HttpClient 都写一下了
services.ConfigureHttpClientDefaults(c =>
{
c.AddAsKeyed();
});
使用默认 HttpClient 配置和单个 HttpClient 的配置可以同时使用,单个 HttpClient 的配置生命周期和默认的不一致时会使用单个 HttpClient 的配置
当使用没有注册的名称时会返回默认的 HttpClient
await using var scope = provider.CreateAsyncScope();
var httpClient = scope.ServiceProvider.GetRequiredKeyedService<HttpClient>("default");
Console.WriteLine(httpClient.GetHashCode());
Console.WriteLine(httpClient.BaseAddress);
var httpClient2 = scope.ServiceProvider.GetRequiredKeyedService<HttpClient>("default");
Console.WriteLine(httpClient2.GetHashCode());
另外我们可以通过 RemoveAsKeyed()
方法移除 HttpClient 的 keyed service 注册
public static IHttpClientBuilder RemoveAsKeyed(this IHttpClientBuilder builder)
Implement
它的内部是怎么实现的呢,实际在 httpClient 中注册了一个 keyed service,我们可以反编译或者从源代码中看一看
首先看看 AddAsKeyed()
方法
public static IHttpClientBuilder AddAsKeyed(
this IHttpClientBuilder builder,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
ThrowHelper.ThrowIfNull((object) builder, nameof (builder));
string name = builder.Name;
IServiceCollection services = builder.Services;
HttpClientMappingRegistry mappingRegistry = services.GetMappingRegistry();
if (name == null)
{
mappingRegistry.DefaultKeyedLifetime?.RemoveRegistration(services);
mappingRegistry.DefaultKeyedLifetime = new HttpClientKeyedLifetime(lifetime);
mappingRegistry.DefaultKeyedLifetime.AddRegistration(services);
}
else
{
HttpClientKeyedLifetime clientKeyedLifetime1;
if (mappingRegistry.KeyedLifetimeMap.TryGetValue(name, out clientKeyedLifetime1))
clientKeyedLifetime1.RemoveRegistration(services);
HttpClientKeyedLifetime clientKeyedLifetime2 = new HttpClientKeyedLifetime(name, lifetime);
mappingRegistry.KeyedLifetimeMap[name] = clientKeyedLifetime2;
clientKeyedLifetime2.AddRegistration(services);
}
return builder;
}
HttpClientMapingRegistry
是一个 mapping 关系和默认的 HttpClient 的生命周期
internal sealed class HttpClientMappingRegistry
{
public Dictionary<string, Type> NamedClientRegistrations { get; } = new();
public Dictionary<string, HttpClientKeyedLifetime> KeyedLifetimeMap { get; } = new();
public HttpClientKeyedLifetime? DefaultKeyedLifetime { get; set; }
}
HttpClientKeyedLifetime
实现如下:
internal classHttpClientKeyedLifetime
{
publicstaticreadonly HttpClientKeyedLifetime Disabled = new(null!, null!, null!);
publicobject ServiceKey { get; }
public ServiceDescriptor Client { get; }
public ServiceDescriptor Handler { get; }
publicbool IsDisabled => ReferenceEquals(this, Disabled);
private HttpClientKeyedLifetime(object serviceKey, ServiceDescriptor client, ServiceDescriptor handler)
{
ServiceKey = serviceKey;
Client = client;
Handler = handler;
}
private HttpClientKeyedLifetime(object serviceKey, ServiceLifetime lifetime)
{
ThrowHelper.ThrowIfNull(serviceKey);
ServiceKey = serviceKey;
Client = ServiceDescriptor.DescribeKeyed(typeof(HttpClient), ServiceKey, CreateKeyedClient, lifetime);
Handler = ServiceDescriptor.DescribeKeyed(typeof(HttpMessageHandler), ServiceKey, CreateKeyedHandler, lifetime);
}
public HttpClientKeyedLifetime(ServiceLifetime lifetime) : this(KeyedService.AnyKey, lifetime) { }
public HttpClientKeyedLifetime(string name, ServiceLifetime lifetime) : this((object)name, lifetime) { }
public void AddRegistration(IServiceCollection services)
{
if (IsDisabled)
{
return;
}
services.Add(Client);
services.Add(Handler);
}
public void RemoveRegistration(IServiceCollection services)
{
if (IsDisabled)
{
return;
}
services.Remove(Client);
services.Remove(Handler);
}
private static HttpClient CreateKeyedClient(IServiceProvider serviceProvider, object? key)
{
if (key is not string name || IsKeyedLifetimeDisabled(serviceProvider, name))
{
returnnull!;
}
return serviceProvider.GetRequiredService<IHttpClientFactory>().CreateClient(name);
}
private static HttpMessageHandler CreateKeyedHandler(IServiceProvider serviceProvider, object? key)
{
if (key is not string name || IsKeyedLifetimeDisabled(serviceProvider, name))
{
returnnull!;
}
HttpMessageHandler handler = serviceProvider.GetRequiredService<IHttpMessageHandlerFactory>().CreateHandler(name);
// factory will return a cached instance, wrap it to be able to respect DI lifetimes
returnnew LifetimeTrackingHttpMessageHandler(handler);
}
private static bool IsKeyedLifetimeDisabled(IServiceProvider serviceProvider, string name)
{
HttpClientMappingRegistry registry = serviceProvider.GetRequiredService<HttpClientMappingRegistry>();
if (!registry.KeyedLifetimeMap.TryGetValue(name, out HttpClientKeyedLifetime? registration))
{
registration = registry.DefaultKeyedLifetime;
}
return registration?.IsDisabled ?? false;
}
}
可以看到通过 HttpClientKeyedLifetime
来在 service 中注册或者移除 keyed service 的
当在 ConfigureHttpClientDefaults
注册 AddAsKeyed()
时会使用 KeyedService.AnyKey
来注册 HttpClient
不使用 ConfigureHttpClientDefaults
注册 AddAsKeyed()
时根据名称获取不到时就会报错会得到类似下面这样的报错
那么注册了默认的 AddAsKeyed 又移除了单个 HttpClient 的服务会怎么样呢,感兴趣的朋友可以自己尝试一下哈~
References
- .cs
基于 keyed DI 的 HttpClient
基于 keyed DI 的 HttpClient
Intro
.NET 8 中依赖注入引入了 keyed service 的支持 可以参考 .NET 8 中的 KeyedService,.NET 9 中改进了 HttpClient 基于名称的 HttpClient 依赖注入,使用基于名称的 HttpClient 的时候可以直接使用 keyed service 来解析了
Sample
我们可以在 AddHttpClient
之后使用 AddAsKeyed()
方法来注册 keyed service
使用示例如下:
代码语言:javascript代码运行次数:0运行复制var services = new ServiceCollection();
services.AddHttpClient("test1", client =>
{
client.BaseAddress = new Uri("http://localhost:6000");
})
.AddAsKeyed()
;
await using var provider = services.BuildServiceProvider();
var client1 = provider.GetRequiredKeyedService<HttpClient>("test1");
Console.WriteLine(client1.BaseAddress);
注册之后我们就可以从依赖注入容器根据名字获取 HttpClient 服务了如 provider.GetRequiredKeyedService<HttpClient>("test1")
而在之前我们需要使用
scope.ServiceProvider.GetRequiredService<IHttpClientFactory>()
.CreateClient("test1")
使用 keyed service 之后就可以简化一些了,在 asp core 还可以在 API action 方法上使用 [FromKeyedService("test1")HttpClient client]
的方式来使用
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "dotnet");
})
.AddAsKeyed(); // Add HttpClient as a Keyed Scoped service for key="github"
var app = builder.Build();
// Directly inject the Keyed HttpClient by its name
app.MapGet("/", ([FromKeyedServices("github")] HttpClient httpClient) =>
httpClient.GetFromJsonAsync<Repo>("/repos/dotnet/runtime"));
app.Run();
record Repo(string Name, string Url);
默认注册的 HttpClient
服务声明周期为 Scoped
,如果要调整可以
public static IHttpClientBuilder AddAsKeyed(this IHttpClientBuilder builder,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
示例代码如下:
代码语言:javascript代码运行次数:0运行复制var services = new ServiceCollection();
services.AddHttpClient("test1", client =>
{
client.BaseAddress = new Uri("http://localhost:5000");
})
.AddAsKeyed()
;
services.AddHttpClient("test2", client =>
{
client.BaseAddress = new Uri("http://localhost:6000");
})
.AddAsKeyed(ServiceLifetime.Singleton)
;
awaitusingvar provider = services.BuildServiceProvider();
{
awaitusingvar scope = provider.CreateAsyncScope();
var client1 = scope.ServiceProvider.GetRequiredKeyedService<HttpClient>("test1");
Console.WriteLine(client1.GetHashCode());
Console.WriteLine(client1.BaseAddress);
var client2 = scope.ServiceProvider.GetRequiredKeyedService<HttpClient>("test2");
Console.WriteLine(client2.GetHashCode());
Console.WriteLine(client2.BaseAddress);
}
{
awaitusingvar scope = provider.CreateAsyncScope();
var client1 = scope.ServiceProvider.GetRequiredKeyedService<HttpClient>("test1");
Console.WriteLine(client1.GetHashCode());
Console.WriteLine(client1.BaseAddress);
var client2 = scope.ServiceProvider.GetRequiredKeyedService<HttpClient>("test2");
Console.WriteLine(client2.GetHashCode());
Console.WriteLine(client2.BaseAddress);
}
这里这两个 HttpClient 一个是默认的生命周期一个是指定的 Singleton
,然后创建了两个 scope 输出,两个 scope 的 HttpClient 应该不相同,Singleton 则应该相同
输出结果如下:
如果有很多个 HttpClient 注册,为每个 HttpClient 都写一遍 AddAsKey()
也挺繁琐的,我们可以使用 .NET 8 中引入的 ConfigureHttpClientDefaults
来将所有的 HttpClient 都注册为 named HttpClient
就无需每个 HttpClient 都写一下了
services.ConfigureHttpClientDefaults(c =>
{
c.AddAsKeyed();
});
使用默认 HttpClient 配置和单个 HttpClient 的配置可以同时使用,单个 HttpClient 的配置生命周期和默认的不一致时会使用单个 HttpClient 的配置
当使用没有注册的名称时会返回默认的 HttpClient
await using var scope = provider.CreateAsyncScope();
var httpClient = scope.ServiceProvider.GetRequiredKeyedService<HttpClient>("default");
Console.WriteLine(httpClient.GetHashCode());
Console.WriteLine(httpClient.BaseAddress);
var httpClient2 = scope.ServiceProvider.GetRequiredKeyedService<HttpClient>("default");
Console.WriteLine(httpClient2.GetHashCode());
另外我们可以通过 RemoveAsKeyed()
方法移除 HttpClient 的 keyed service 注册
public static IHttpClientBuilder RemoveAsKeyed(this IHttpClientBuilder builder)
Implement
它的内部是怎么实现的呢,实际在 httpClient 中注册了一个 keyed service,我们可以反编译或者从源代码中看一看
首先看看 AddAsKeyed()
方法
public static IHttpClientBuilder AddAsKeyed(
this IHttpClientBuilder builder,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
ThrowHelper.ThrowIfNull((object) builder, nameof (builder));
string name = builder.Name;
IServiceCollection services = builder.Services;
HttpClientMappingRegistry mappingRegistry = services.GetMappingRegistry();
if (name == null)
{
mappingRegistry.DefaultKeyedLifetime?.RemoveRegistration(services);
mappingRegistry.DefaultKeyedLifetime = new HttpClientKeyedLifetime(lifetime);
mappingRegistry.DefaultKeyedLifetime.AddRegistration(services);
}
else
{
HttpClientKeyedLifetime clientKeyedLifetime1;
if (mappingRegistry.KeyedLifetimeMap.TryGetValue(name, out clientKeyedLifetime1))
clientKeyedLifetime1.RemoveRegistration(services);
HttpClientKeyedLifetime clientKeyedLifetime2 = new HttpClientKeyedLifetime(name, lifetime);
mappingRegistry.KeyedLifetimeMap[name] = clientKeyedLifetime2;
clientKeyedLifetime2.AddRegistration(services);
}
return builder;
}
HttpClientMapingRegistry
是一个 mapping 关系和默认的 HttpClient 的生命周期
internal sealed class HttpClientMappingRegistry
{
public Dictionary<string, Type> NamedClientRegistrations { get; } = new();
public Dictionary<string, HttpClientKeyedLifetime> KeyedLifetimeMap { get; } = new();
public HttpClientKeyedLifetime? DefaultKeyedLifetime { get; set; }
}
HttpClientKeyedLifetime
实现如下:
internal classHttpClientKeyedLifetime
{
publicstaticreadonly HttpClientKeyedLifetime Disabled = new(null!, null!, null!);
publicobject ServiceKey { get; }
public ServiceDescriptor Client { get; }
public ServiceDescriptor Handler { get; }
publicbool IsDisabled => ReferenceEquals(this, Disabled);
private HttpClientKeyedLifetime(object serviceKey, ServiceDescriptor client, ServiceDescriptor handler)
{
ServiceKey = serviceKey;
Client = client;
Handler = handler;
}
private HttpClientKeyedLifetime(object serviceKey, ServiceLifetime lifetime)
{
ThrowHelper.ThrowIfNull(serviceKey);
ServiceKey = serviceKey;
Client = ServiceDescriptor.DescribeKeyed(typeof(HttpClient), ServiceKey, CreateKeyedClient, lifetime);
Handler = ServiceDescriptor.DescribeKeyed(typeof(HttpMessageHandler), ServiceKey, CreateKeyedHandler, lifetime);
}
public HttpClientKeyedLifetime(ServiceLifetime lifetime) : this(KeyedService.AnyKey, lifetime) { }
public HttpClientKeyedLifetime(string name, ServiceLifetime lifetime) : this((object)name, lifetime) { }
public void AddRegistration(IServiceCollection services)
{
if (IsDisabled)
{
return;
}
services.Add(Client);
services.Add(Handler);
}
public void RemoveRegistration(IServiceCollection services)
{
if (IsDisabled)
{
return;
}
services.Remove(Client);
services.Remove(Handler);
}
private static HttpClient CreateKeyedClient(IServiceProvider serviceProvider, object? key)
{
if (key is not string name || IsKeyedLifetimeDisabled(serviceProvider, name))
{
returnnull!;
}
return serviceProvider.GetRequiredService<IHttpClientFactory>().CreateClient(name);
}
private static HttpMessageHandler CreateKeyedHandler(IServiceProvider serviceProvider, object? key)
{
if (key is not string name || IsKeyedLifetimeDisabled(serviceProvider, name))
{
returnnull!;
}
HttpMessageHandler handler = serviceProvider.GetRequiredService<IHttpMessageHandlerFactory>().CreateHandler(name);
// factory will return a cached instance, wrap it to be able to respect DI lifetimes
returnnew LifetimeTrackingHttpMessageHandler(handler);
}
private static bool IsKeyedLifetimeDisabled(IServiceProvider serviceProvider, string name)
{
HttpClientMappingRegistry registry = serviceProvider.GetRequiredService<HttpClientMappingRegistry>();
if (!registry.KeyedLifetimeMap.TryGetValue(name, out HttpClientKeyedLifetime? registration))
{
registration = registry.DefaultKeyedLifetime;
}
return registration?.IsDisabled ?? false;
}
}
可以看到通过 HttpClientKeyedLifetime
来在 service 中注册或者移除 keyed service 的
当在 ConfigureHttpClientDefaults
注册 AddAsKeyed()
时会使用 KeyedService.AnyKey
来注册 HttpClient
不使用 ConfigureHttpClientDefaults
注册 AddAsKeyed()
时根据名称获取不到时就会报错会得到类似下面这样的报错
那么注册了默认的 AddAsKeyed 又移除了单个 HttpClient 的服务会怎么样呢,感兴趣的朋友可以自己尝试一下哈~
References
- .cs
本文标签: 基于 keyed DI 的 HttpClient
版权声明:本文标题:基于 keyed DI 的 HttpClient 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/jiaocheng/1748097852a2252319.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论