19

I am trying to understand how HttpClient has been implemented for Captcha in Nop Commerce and for the sake of testability how creating new instance of HttpClient has been manage in Nop Commerce project.

I came across ValidateCaptchaAttribute and ValidateCaptchaFilter and I see HttpClient has been wrapped inside CaptchaHttpClient class but I don't understand from where does CaptchaHttpClient receive dependency for HttpClient and from where constructor of CaptchaHttpClient class is being called.

Inside ServiceCollectionExtensions class I see below code:

public static void AddNopHttpClients(this IServiceCollection services)
 {
    //default client
    services.AddHttpClient(NopHttpDefaults.DefaultHttpClient).WithProxy();

    //client to request current store
    services.AddHttpClient<StoreHttpClient>();

    //client to request nopCommerce official site
    services.AddHttpClient<NopHttpClient>().WithProxy();

    //client to request reCAPTCHA service
    services.AddHttpClient<CaptchaHttpClient>().WithProxy();
 }

But I don't see where HttpClient object is created:

var client = new HttpClient() // Where this is done?

Am I perhaps missing something?

Nop Commerce Version = 4.20

halfer
  • 19,824
  • 17
  • 99
  • 186
I Love Stackoverflow
  • 6,738
  • 20
  • 97
  • 216

2 Answers2

44

From the documentation:

Adds the IHttpClientFactory and related services to the IServiceCollection and configures a binding between the TClient type and a named HttpClient. The client name will be set to the type name of TClient.

Roughly translated, services.AddHttpClient<CaptchaHttpClient>() means that CaptchaHttpClient has a dependency on HttpClient. This says that when injecting HttpClient into CaptchaHttpClient, don't just create a new one - use an implementation of IHttpClientFactory to provide one and inject the HttpClient it creates.

This means that you're not managing the lifetime of the HttpClient. The ServiceProvider is doing that behind the scenes. (You're not responsible for creating the client factory either.)

This documentation explains why this exists and how it works.

A Typed Client is, effectively, a transient object, meaning that a new instance is created each time one is needed and it will receive a new HttpClient instance each time it's constructed. However, the HttpMessageHandler objects in the pool are the objects that are reused by multiple Http requests.

This means:

  • The thing you're registering - in this case CaptchaHttpClient is transient so that each time it's resolved, a new instance is created.
  • Each time it is created, a new HttpClient is created and injected.
  • Although the HttpClient is new, the HttpMessageHandler it depends upon is reused.

This uses a pool of HttpMessageHandler instances that we don't have to manage. Our class just depends on HttpClient without having to worry about the negative side effects that happen when we create/dispose an HttpClient each time we need one.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • Thank you so much for the answer.So does that mean AddHttpClient is doing new HttpClient() behind the scenes? – I Love Stackoverflow May 31 '19 at 13:37
  • 1
    Here's some [more documentation](https://learn.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests). I gathered that this exists because developers were using `HttpClient` wrong, but I don't know what it does to make it right. So now I'm reading this too. – Scott Hannen May 31 '19 at 13:42
  • 1
    This last documentation link is really helpful and cleared some of my doubts.Thanks alot.But still I dont know what does WithProxy() means?Does it creates HttpClient as Singleton object or what it does exactly? – I Love Stackoverflow May 31 '19 at 13:48
  • 1
    That's [here](https://github.com/nopSolutions/nopCommerce/blob/develop/src/Presentation/Nop.Web.Framework/Infrastructure/Extensions/HttpClientBuilderExtensions.cs), back in the Nop source code. Apparently it gets some proxy settings from configuration and configures the `HttpMessageHandler` to use them. – Scott Hannen May 31 '19 at 13:58
  • Suppose If I dont want to use this method WithProxy(),then what other suitable method I should use on AddHttpClient? – I Love Stackoverflow May 31 '19 at 14:01
  • 1
    Wherever it gets those proxy settings from, if they aren't specified then default credentials are used. So, while you can decide whether you want to use `WithProxy()` in your own code is an option, this is in Nop's code. So unless they provide some way to override all of this configuration you're just using the dependencies however they registered them. But since it doesn't appear to do anything unless the configuration settings provide proxy credentials, it seems like you could just ignore it. – Scott Hannen May 31 '19 at 14:05
  • in background service it's live evertime :(( And ServerCertificatCustomValidationCallback used only on first start... and some times.... it's crazy – xSx Jun 30 '21 at 14:14
  • This seems opposite of the general guidance that the same instance of a HttpClient should be used for the lifetime of the process, and it can even cause memory leaks if you treat it as a short lived object: https://stackoverflow.com/a/15708633/5099097 – Joe Eng Apr 13 '22 at 23:07
  • 2
    That's true, but it's because of the HttpMessageHandler. It's used by the HttpClient, and it's the thing that really needs to be reused. – Scott Hannen Apr 13 '22 at 23:51
5

I found this article helpful in furthering my understanding of IHttpClientFactory patterns.

When defining typed clients in your ConfigureServices method, the typed service is registered with transient scope. This means that a new instance is created by the DI container every time one is needed. The reason this occurs is that a [sic] HttpClient instance is injected into the typed client instance. That HttpClient instance is intended to be short lived so that the HttpClientFactory can ensure that the underlying handlers (and connections) are released and recycled.

bubbleking
  • 3,329
  • 3
  • 29
  • 49
golfalot
  • 956
  • 12
  • 22