• .NET Core 3.0之深入源码理解HttpClientFactory(二)


     

    写在前面

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

    详细介绍

    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:  }
  • 相关阅读:
    javascript里面this机制的几个例子
    把List<string>集合,编程string,并以“,”号分割
    比较集合List<T>集合,前后多了哪些数据,少了哪些数据Except
    c# Web.config中 windows连接数据库
    MVC之图片验证码
    匿名函数-简单实例
    c# 如何找到项目中图片的相对路径
    MVC下 把数据库中的byte[]值保存成图片,并显示在view页面
    MVC下form表单一次上传多种类型的图片(每种类型的图片可以上传多张)
    关于Visual Studio未能加载各种Package包的解决
  • 原文地址:https://www.cnblogs.com/edison0621/p/11261373.html
Copyright © 2020-2023  润新知