How should I use the HttpClient in an ASP.NET Core 2.0 API
Asked Answered
S

3

6

I am working on an ASP.NET Core 2.0 API and my API needs to make calls to another, 3rd party, REST API to upload and retrieve files and get file lists and status information. I will be hosting the API in Azure and plan to do Blue-Green deployments between my staging and production slots.

It seems that the general consensus for best practice is to set up a Singleton instance of the HTTPClient via DI registration in Startup.cs-->ConfigureSerrvices method, in order to improve performance and avoid socket errors that can occur if I new up an dispose the HTTPClient connection with each use via a Using statement. This is noted in the following links;

https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

https://msdn.microsoft.com/en-us/library/system.net.http.httpclient(v=vs.110).aspx#Anchor_5

http://www.nimaara.com/2016/11/01/beware-of-the-net-httpclient/

But if I do that, then I can face an issue where the Singleton instance will not see any DNS change when I do a Blue-Green deployment in Azure. This is noted in the following links;

http://byterot.blogspot.co.uk/2016/07/singleton-httpclient-dns.html

https://github.com/dotnet/corefx/issues/11224

http://www.nimaara.com/2016/11/01/beware-of-the-net-httpclient/

So.. the general consensus now is to use a static HTTPClient instance but control the ConnectionLeaseTimeout value of the ServicePoint class to set it to a shorter value that will force-close the connection to get a refresh of the DNS. This blog post even talks about a nice RestClient component in a nuget package (Easy.Common by Nima) that properly addresses ConnectionLeaseTimeout as well as the cached DNS values.

However, it seems that ASP.NET Core 2.0 does not fully implement the ServicePoint and so this approach is not really currently supported in ASP.Net Core 2.0.

Can anyone advise me as to the correct approach I should pursue for using HttpClient in my ASP.NET Core 2.0 API running on Azure? I want to be able to do Blue-Green deployments. So, should I just resort to the Using statement and new up the client with each use and just suffer the performance hit?

It just seems like there has to be a reliable and performant solution to this common need.

Skiagraph answered 2/1, 2018 at 18:50 Comment(3)
How you host and deploy your API on Azure isn't relevant. DNS changes to the 3rd party API are what you should be thinking about, not DNS changes to your API.Aprilette
@ToddMenier - Thanks for pointing that out. So, if I use the same approach to the HttpClient as a singleton in my MVC app that will consume this API, that is where I need to be concerned out DNS refresh when I roll updates and do a Blue-Green flip.Skiagraph
Yes. Any consumer of your API could potentially be affected by that flip, not the API itself as a consumer of some 3rd-party API.Aprilette
A
9

The problem with some of the articles you've linked to is that they've resulted in a widespread belief that setting ConnectionLeaseTimeout does some sort of black magic way down in the sockets layer, and that if you're on a platform that doesn't support it, you're screwed. Those articles do a disservice by not touching on what that setting actually does, which is send a Connection: Close header to the server being called at regular intervals. That's it. I've verified this from the source, and it's very easy to replicate. In fact I've done it myself in my Flurl library, implementation details here and here.

That said, I personally find the DNS problem to be a bit overblown. Note, for example, that connections are automatically closed after sitting idle for a period of time (100 seconds by default). The benefits of using HttpClient as a singleton far outweigh the risks.

My advice would be to use an instance per 3rd-party service being called. The thinking here is you get maximum reuse while still taking advantage of things like DefaultRequestHeaders, which tend to be specific to one service. If you're only calling one service, that's just a singleton. (Conversely, if you're calling 1000 different services, you can't avoid 1000 open sockets any way you slice it.) If you don't expect the connection to ever go idle for long and want to be defensive about possible DNS switches with the 3rd-party service, send a Connection: close header, or simply dispose and recreate the HttpClient, at regular intervals. And note that this is a trade-off, not a perfect solution, that should help mitigate the problem.

Aprilette answered 3/1, 2018 at 3:39 Comment(3)
In my use case, I am calling one specific web service many times so what you are saying makes sense.Skiagraph
It just occurred to me that if I am using a singleton and my calls to the web service require different username/password auth headers (even though it is the same base URL), is there a chance that setting the header before I make the async request my cause problems?Skiagraph
Nope, just make sure you're setting them on HttpRequestMessage.Headers, not HttpClient.DefaultRequestHeaders.Aprilette
S
3

The documentation is confusing and the related articles claiming that you should only ever have one HttpClient instance are also some misleading (or misled, depending on the perspective).

The advice is to have only one instance per application lifecycle, but in truth, when you're talking about a web application, that should be confined to request lifecycle. When the documentation warns about newing up an HttpClient for every request, it is talking about every request made via HttpClient, not every request served by your web application that just happens to be utilizing HttpClient.

Long and short, yes, you should avoid using with HttpClient, but having a request-scoped instance is just fine. In other words, it doesn't need to be a singleton.

Shiekh answered 2/1, 2018 at 19:25 Comment(4)
when you say request-scoped instance, are you saying I should new up an instance of the HttpClient in the request method?Skiagraph
No, just that you should have an instance per request. You can achieve that any number of ways: DI, controller ivar, etc.Shiekh
Hmm. this could be appropriate in some scenarios, but I'm not sure it should be presented as a one-size-fits-all solution. If each incoming request results in just 1 call to the 3rd party API, this is no better than wrapping that call in a using statement. And in a high-traffic, high-concurrency scenario, it's very conceivable that you could bump into the same issues of excessive DSN lookups and open sockets that he's trying to avoid.Aprilette
Your advice is the same as wrapping the call in a 'using' statement and will end up into the very same problems.Forelimb
B
1

As of 2.1, a good option is to use IHttpClientFactory.

Microsoft Docs | Announcement blog post

Buckram answered 27/7, 2018 at 15:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.