写在前面


上一篇文章讨论了通过在ConfigureServices中调用services.AddHttpClient()方法,并基于此进一步探讨了DefaultHttpClientFactory是如何创建HttpClient实例和HttpMessageHandler实例的,并了解了DefaultHttpClientFactory内部维护者一个定时器和两个HttpMessageHandler对象集合,以定期清理无效的
HttpMessageHandler对象,详细的内容可以点击链接
<https://www.cnblogs.com/edison0621/p/11224890.html>跳转,接下来我会接着前一篇文章继续展开相关讨论。

详细介绍

HttpMessageHandlerBuilder


该类是一个抽象类,起到生成器的作用,可用于用于配置HttpMessageHandler实例。HttpMessageHandlerBuilder会在ServiceCollection中被注册为Transient服务。调用方要为每个要创建的HttpMessageHandler实例检索一个新实例。实现者应该确保每个实例都只使用一次。

HttpMessageHandlerBuilder里面有三个比较重要的属性:
1: /// <summary> 2: /// 主HttpMessageHandler实例 3: /// </summary> 4: public
abstract HttpMessageHandler PrimaryHandler { get; set; } 5:   6: /// <summary>
7:/// 这个是一个附加实例,用于配置HttpClient管道 8: /// </summary> 9: public abstract
IList<DelegatingHandler> AdditionalHandlers { get; } 10:   11: /// <summary>
12:/// 可用于从依赖项注入容器解析服务的IServiceProvider 13: /// </summary> 14: public virtual
IServiceProvider Services { get; }
这三个属性意味着每个HttpMessageHandlerBuilder都需要维护自身的HttpMessageHandler实例和管道。

其内部还有一个抽象方法:
1: public abstract HttpMessageHandler Build();

当然,内部最核心的方法就是管道的创建过程了,需要传入主派生类自身的HttpMessageHandler和管道列表对象。它会将primaryHandler实例付给管道列表的第一个Item的InnerHandler,其他对象会依此后移,这也为我们自定义HttpMessageHandler(各种中间件)提供了无限可能。

相关实现如下:
1: var next = primaryHandler; 2: for (var i = additionalHandlersList.Count -
1; i >= 0; i--) 3: { 4: var handler = additionalHandlersList[i]; 5: if
(handler ==null) 6: { 7: var message =
Resources.FormatHttpMessageHandlerBuilder_AdditionalHandlerIsNull(nameof(additionalHandlers));
8: throw new InvalidOperationException(message); 9: } 10:   11: if
(handler.InnerHandler !=null) 12: { 13: var message =
Resources.FormatHttpMessageHandlerBuilder_AdditionHandlerIsInvalid( 14:
nameof(DelegatingHandler.InnerHandler), 15: nameof(DelegatingHandler), 16:
nameof(HttpMessageHandlerBuilder), 17: Environment.NewLine, 18: handler);
19: throw new InvalidOperationException(message); 20: } 21:   22:
handler.InnerHandler = next; 23: next = handler; 24: }

接下来我们看一下HttpMessageHandlerBuilder一个派生类DefaultHttpMessageHandlerBuilder,其构造函数会传入IServiceProvider实例,我们的自定义操作也可以参照这个类。

关于Build方法的实现如下,比较简单主要是调用了CreateHandlerPipeline方法:
1: public override HttpMessageHandler Build() 2: { 3: if (PrimaryHandler ==
null) 4: { 5: var message =
Resources.FormatHttpMessageHandlerBuilder_PrimaryHandlerIsNull(nameof(PrimaryHandler));
6: throw new InvalidOperationException(message); 7: } 8: 9: return
CreateHandlerPipeline(PrimaryHandler, AdditionalHandlers); 10: }
ITypedHttpClientFactory


这是一个抽象工厂,该组件可以使用给定逻辑名称的自定义配置创建类型化HttpClient实例,与命名方式创建HttpClient具有相同的的功能。类型化客户端可能用于单个后端终结点,并封装此终结点的所有处理逻辑。
另一个优势是它们使用 DI 被注入到应用中需要的位置,下一篇文章会再次讨论相关功能。

我们首先看一下调用方式:
1: public static IHttpClientBuilder AddHttpClient<TClient>(this
IServiceCollection services) 2: where TClient : class 3: { 4: if (services ==
null) 5: { 6: throw new ArgumentNullException(nameof(services)); 7: } 8:  
9: AddHttpClient(services); 10:   11: var name =
TypeNameHelper.GetTypeDisplayName(typeof(TClient), fullName: false); 12: var
builder =new DefaultHttpClientBuilder(services, name); 13:
builder.AddTypedClient<TClient>(); 14: return builder; 15: }

可以看出此处的调用与普通的HttpClient没有什么太大区别,只是增加了一个泛型标记,而且该类型没有特殊的要求,只要是个类就行。其内部依然调用AddHttpClient(services),但它调用了另一个扩展方法,如下所示:
1: public static IHttpClientBuilder AddTypedClient<TClient>(this
IHttpClientBuilder builder) 2: where TClient : class 3: { 4: if (builder ==
null) 5: { 6: throw new ArgumentNullException(nameof(builder)); 7: } 8:  
9: builder.Services.AddTransient<TClient>(s => 10: { 11: var
httpClientFactory = s.GetRequiredService<IHttpClientFactory>(); 12: var
httpClient = httpClientFactory.CreateClient(builder.Name); 13:   14: var
typedClientFactory = s.GetRequiredService<ITypedHttpClientFactory<TClient>>();
15: return typedClientFactory.CreateClient(httpClient); 16: }); 17:   18:
return builder; 19: }

可以看到最终的代码调用了ITypedHttpClientFactory的CreateClient方法,Microsoft.Extensions.Http包中有一个默认的ITypedHttpClientFactory派生类,DefaultTypedHttpClientFactory<TClient>,该类提供了了构造函数用于接收IServiceProvider实例,以及一个内部类声明的缓存对象,该对象十分重要,它被注册为singleton类型,已达到全局使用,并可以充当相关实例激活时的对象池。它也允许它的外部类注册为transient,这样它就不会在应用根服务提供程序上被关掉了。

相关代码如下:
1: public TClient CreateClient(HttpClient httpClient) 2: { 3: if
(httpClient ==null) 4: { 5: throw new
ArgumentNullException(nameof(httpClient)); 6: } 7:   8: return
(TClient)_cache.Activator(_services,new object[] { httpClient }); 9: }
内部缓存对象:
1: public class Cache 2: { 3: private readonly static Func<ObjectFactory>
_createActivator = () => ActivatorUtilities.CreateFactory(typeof(TClient), new
Type[] {typeof(HttpClient), }); 4:   5: private ObjectFactory _activator; 6:
private bool _initialized; 7: private object _lock; 8:   9: public
ObjectFactory Activator => LazyInitializer.EnsureInitialized( 10: ref
_activator, 11: ref _initialized, 12: ref _lock, 13: _createActivator); 14:
}
最后我们看一下源码中提供的范例:
1: class ExampleClient 2: { 3: private readonly HttpClient _httpClient; 4:
private readonly ILogger _logger; 5: // typed clients can use constructor
injection to access additional services 6: public ExampleClient(HttpClient
httpClient, ILogger<ExampleClient> logger) 7: { 8: _httpClient = httpClient;
9: _logger = logger; 10: } 11: // typed clients can expose the HttpClient
for application code to call directly 12: public HttpClient HttpClient =>
_httpClient; 13: // typed clients can also define methods that abstract usage
of the HttpClient 14: public async Task SendHelloRequest() 15: { 16: var
response = await _httpClient.GetAsync("/helloworld"); 17:
response.EnsureSuccessStatusCode(); 18: } 19: } 20: //This sample shows how
to consume a typed client from an ASP.NET Core middleware. 21: public void
Configure(IApplicationBuilder app, ExampleClient exampleClient) 22: { 23:
app.Run(async (context) => 24: { 25: var response = await
_exampleClient.GetAsync("/helloworld"); 26: await context.Response.WriteAsync(
"Remote server said: "); 27: await
response.Content.CopyToAsync(context.Response.Body); 28: }); 29: } 30:
//This sample shows how to consume a typed client from an ASP.NET Core MVC
Controller. 31: public class HomeController :
ControllerBase(IApplicationBuilder app, ExampleClient exampleClient) 32: { 33:
private readonly ExampleClient _exampleClient; 34: public
HomeController(ExampleClient exampleClient) 35: { 36: _exampleClient =
exampleClient; 37: } 38: public async Task<IActionResult> Index() 39: {
40: var response = await _exampleClient.GetAsync("/helloworld"); 41: var text
= await response.Content.ReadAsStringAsync(); 42: return Content("Remote
server said: " + text, "text/plain"); 43: }; 44: }