Written in front

The previous article discussed how to create the HttpClient instance and the HttpMessageHandler instance by calling the services.AddHttpClient() method in ConfigureServices, and further understands that the DefaultHttpClientFactory internal maintainer has a timer and two HttpMessageHandler objects. Collection, to clean up the invalid HttpMessageHandler object regularly, the detailed content can be clicked on the
link to
jump, then I will continue the discussion with the previous article.

Detailed introduction

HttpMessageHandlerBuilder

This class is an abstract class that acts as a generator and can be used to configure an HttpMessageHandler instance. The HttpMessageHandlerBuilder will be registered as a Transient service in the ServiceCollection. The caller retrieves a new instance for each HttpMessageHandler instance to be created. Implementers should ensure that each instance is used only once.

There are three more important properties in HttpMessageHandlerBuilder:

   1:  /// <summary>
   2:   /// main HttpMessageHandler instance
   3:  /// </summary>
   4:  public abstract HttpMessageHandler PrimaryHandler { get; set; }
   5:   
   6:  /// <summary>
   7:   /// This is an additional instance for configuring the HttpClient pipeline
   8:  /// </summary>
   9:  public abstract IList<DelegatingHandler> AdditionalHandlers { get; }
  10:   
  11:  /// <summary>
  12:   /// IServiceProvider that can be used to inject container resolution services from dependencies
  13:  /// </summary>
  14:  public virtual IServiceProvider Services { get; }

These three properties mean that each HttpMessageHandlerBuilder needs to maintain its own HttpMessageHandler instance and pipeline.

There is also an abstract method inside:

   1:  public abstract HttpMessageHandler Build();

Of course, the innermost core method is the creation process of the pipeline. You need to pass in the HttpMessageHandler and the pipeline list object of the main derived class itself. It will give the primaryHandler instance to the InnerHandler of the first Item in the pipeline list, and other objects will move backwards. This also provides unlimited possibilities for our custom HttpMessageHandler (various middleware).

The relevant implementation is as follows:

   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:               trades);
  19:          throw new InvalidOperationException(message);
  20:       }
  twenty one:   
  22:       handler.InnerHandler = next;
  23:      next = handler;
  24:   }

Next, let’s take a look at HttpMessageHandlerBuilder, a derived class DefaultHttpMessageHandlerBuilder whose constructor will pass in the IServiceProvider instance. Our custom operation can also refer to this class.

The implementation of the Build method is as follows, the simpler is mainly to call the CreateHandlerPipeline method:

   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

This is an abstract factory that creates a typed HttpClient instance with a custom configuration of the given logical name, with the same functionality as the naming method for creating HttpClient. A typed client might be used for a single backend endpoint and encapsulate all the processing logic for that endpoint. Another advantage is that they use DI to be injected into the location needed in the application, and the next article will discuss the related features again.

Let’s first look at the calling method:

   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:   }

It can be seen that the call here is not much different from the normal HttpClient, except that a generic tag is added, and there is no special requirement for the type, as long as it is a class. It still calls AddHttpClient(services) internally, but it calls another extension method like this:

   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:   }

You can see that the final code calls the CreateClient method of ITypedHttpClientFactory. The Microsoft.Extensions.Http package has a default ITypedHttpClientFactory derived class, DefaultTypedHttpClientFactory<TClient>, which provides a constructor for receiving IServiceProvider instances, and an inner class. Declared cache object, this object is very important, it is registered as a singleton type, has been used globally, and can serve as a pool of objects when the relevant instance is activated. It also allows its external class to be registered as transient so that it is not turned off on the application root service provider.

The relevant code is as follows:

   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:   }

Internal cache object:

   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:   }

Finally, let’s take a look at the examples provided in the source code:

   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:   }

Orignal link:https://www.cnblogs.com/edison0621/p/11261373.html