Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using HttpClientFactory without dependency injection #28385

Closed
poke opened this issue Apr 4, 2019 · 109 comments
Closed

Using HttpClientFactory without dependency injection #28385

poke opened this issue Apr 4, 2019 · 109 comments
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions feature-httpclientfactory Includes: HttpClientFactory (some bugs also in Extensions repo) question
Milestone

Comments

@poke
Copy link
Contributor

poke commented Apr 4, 2019

Is there any way to consume the HttpClientFactory without using dependency injection, or more precisely without using M.E.DI?

I am creating a client library for which I want to make use of HttpClientFactory but I am going to use this library also in a legacy project that does not not have a M.E.DI-compatible DI.

Ideally, I would just new up a DefaultHttpClientFactory and consume that directly but since the factory itself is internal, this is not possible.

Why is the HttpClientFactory internal and is there any way to consume it explicitly without using DI?

@rynowak
Copy link
Member

rynowak commented Apr 7, 2019

The answer to this really depends what you expect to get out of using the client factory. I get asked this question a lot, and the answer depends on a lot more details about what you want to accomplish. It sounds like you might have a few different scenarios so I'm providing a lot of information with the hopes that it's helpful.

If you are building a library that you plan to distribute, I would strongly suggest that you don't take a dependency on IHttpClientFactory at all, and have your consumers pass in an HttpClient instance.

If you are building a non-ASP.NET-Core application, consider using Microsoft.Extensions.Hosting to create a DI container.

If you are building a non-ASP.NET-Core application and you really don't want to use Microsoft.Extensions.Hosting then it depends what you want to use the client factory for.

A. I want an opinionated builder abstraction for configuring HttpClientFactory.
B. I don't need that. I just want an HttpClient with good network behavior by default.

If you are in category A then I have to explain that DI and IOptions<> is the opinionated builder abstraction. Everything in category A is built on top of DI and IOptions<>. The features exposed by the builder assume that you have a DI system, and we have no plans to try and separate these.

Based on the information you provided, I'm guessing you fall into category B.

If you are on .NET Core - you should use a single HttpClient directly and set SocketsHttpHandler.PooledConnectionTimeout here to an appropriate value.

If you are on .NET Framework - you should use a single HttpClient and use ServicePoint to configure the similar settings.

The good news for anyone interested in connection management is that .NET now has reasonable behavior on Linux (as of 2.1 and SocketsHttpHandler) but it requires configuration.

@rynowak
Copy link
Member

rynowak commented Apr 7, 2019

I've logged a doc issue to clear this up further. dotnet/AspNetCore.Docs#11882

@poke
Copy link
Contributor Author

poke commented Apr 7, 2019

Thank you for the detailed answer (and that doc request), that is going to help a lot!

In my case, I’m somewhere between A and B actually. The client library I am working on is mostly targeted to ASP.NET Core applications, so I have a helper extension method on the service collection that just adds all the clients as typed clients using the HttpClientFactory (using a custom options object to configure a few parameters). That is working just fine the way it is.

However, I now have the requirement to use this in a legacy ASP.NET application that uses Autofac as DI container. So I cannot really use any M.E.DI-specific stuff, and I certainly don’t want to bring all the stuff into that project just to use the HttpClientFactory.

Ideally, what I would like to do is to set up a HttpClientFactory, configure a few clients and then have something I can call to create a factory method that I then register with the existing DI container. What you say makes sense though; the factory makes heavy use of DI and options, so using it without it would be really difficult.

In my case, the various clients each have different DelegatingHandlers that are configured ahead, so using a single HttpClient will not really work for me. But I guess I will just have to set up separate HttpClient instances then for each client.

If you are on .NET Framework - you should use a single HttpClient and use ServicePoint to configure the similar settings.

It would be really nice if there was some guidance along with the docs on what would be good defaults for setting up a HttpClient.

@davidfowl
Copy link
Member

davidfowl commented Apr 13, 2019

However, I now have the requirement to use this in a legacy ASP.NET application that uses Autofac as DI container. So I cannot really use any M.E.DI-specific stuff, and I certainly don’t want to bring all the stuff into that project just to use the HttpClientFactory.

Sure you can, in this mode, think of M.E.DI stuff as an implementation detail for how to get an IHttpClientFactory instance. The other features though, rely on this internal detail in order to do more complex things (like typed clients etc).

Alternatively, if you really wanted to try to integrate the 2 things (autofac and the HttpClientFactory), you can use the ServiceCollection as the configuration API for the HttpClient and use Autofac.Extensions.DependencyInjection to wire it up to your existing autofac container:

public class MyTypedClient
{
    public MyTypedClient(HttpClient client)
    {

    }
}

public class Program
{
    public static void Main(string[] args)
    {
        var services = new ServiceCollection();
        services.AddHttpClient()
                .AddHttpClient<MyTypedClient>();

        var containerBuilder = new ContainerBuilder();
        containerBuilder.Populate(services);
        var container = containerBuilder.Build();
        var factory = container.Resolve<IHttpClientFactory>();
        var typedClient = container.Resolve<MyTypedClient>();
    }
}

It would be really nice if there was some guidance along with the docs on what would be good defaults for setting up a HttpClient.

+1

@uhaciogullari
Copy link

Hey everyone

I've made a fork to address this issue.
https://github.com/uhaciogullari/HttpClientFactoryLite
Let me know what you think.

@alexeyzimarev
Copy link

A. I want an opinionated builder abstraction for configuring HttpClientFactory.
B. I don't need that. I just want an HttpClient with good network behavior by default.

I'd like to stress out that using the DI container for absolutely everything is not the best example of design. Insisting that all developers need to use the container for things like HTTP client factory, in-memory cache, distributed cache, logging and so on, creates impediments for developers that would otherwise avoid using DI containers altogether. I personally prefer to construct my dependency tree in the Startup class explicitly and don't rely on something else that I don't control to resolve dependencies for me.

There's was an issue dotnet/extensions#615 for using ILogger without the container and it is fixed for .NET Core 3. I would expect that all other factories would follow the same approach and expose a way to instantiate factories without using the container, at least for the reason of consistency.

@SittenSpynne
Copy link

SittenSpynne commented Aug 12, 2019

I also feel like this seems like a strange way to strongarm people out of using other DI containers.

I maintain Xamarin Forms projects that predate the M.E.DI stuff. One uses TinyIoC because it chose the FreshMVVM framework. The other uses AutoFac. It would be a PITA for me to rework them to use the Microsoft DI container. I shouldn't have to in order to use HttpClient.

In the AutoFac project, I'd like to reuse a REST utility someone else made for interacting with an internal web service. That utility was written in an ASP .NET Core environment so it already expects me to give it an HttpClient. So he's using the Web Host container and gets all the stuff I want for free.

I don't see good guidance for how to configure AutoFac to do the same thing your DI does. I do see a kind of janky path to set up the M.E.DI container, then somehow convert that to AutoFac. That feels ridiculous. I get that HttpClientFactory is opinionated, but I don't think part of that opinion should be "your app must use Microsoft DI".

Especially in the Xamarin context, I'm bewildered. I've scanned a few articles and issues related to this and I can't tell if I should or shouldn't be using IHttpClientFactory at all. Some seem to suggest I should just set SocketsHttpHandler properties, but that's not available in Xamarin. Some articles vaguely gesture at whether I should configure Xamarin project properties to use platform-specific handlers.

I don't see a clear message for how devs outside of ASP .NET Core are supposed to use HttpClient. I thought when I found this library it would be the clear message. But it looks like even within this library, I see confusing comments that imply outside of ASP .NET Core I might not even want to use HttpClientFactory. Even if that was the wrong read, I don't see an explanation for what the magic inside M.E.DI is so I can realize it with a different DI container (or no container at all.)

If Generic Host container isn't suitable for all platforms, where is the documentation that outlines its limitations and explains, for every Microsoft-supported platform, what the best practice for using HttpClient is? I shouldn't have to read half a dozen blogs to understand such a crucial type, it should be explained by MS.

@kierenj
Copy link

kierenj commented Aug 13, 2019

Incidentally AutoFac has MS DI integration - I think it boils down to a one-liner (builder.Populate(services);).

That said, we went ahead with AutoFac for many internal projects but are struggling to see any advantage to it considering all MS packages use IServiceCollection etc. There's a big old GH issue I have somewhere to remove it just to avoid that one extra step..

@disklosr
Copy link
Contributor

I have a net core unit test project without DI and having a static HttpClientFactory would have been handy to build a HttpClient with many handlers in a one liner using HttpClientFactory.Create(params DelegatingHandler[] handlers)

@JHeLiu
Copy link

JHeLiu commented Sep 26, 2019

A. I want an opinionated builder abstraction for configuring HttpClientFactory.
B. I don't need that. I just want an HttpClient with good network behavior by default.

I'd like to stress out that using the DI container for absolutely everything is not the best example of design. Insisting that all developers need to use the container for things like HTTP client factory, in-memory cache, distributed cache, logging and so on, creates impediments for developers that would otherwise avoid using DI containers altogether. I personally prefer to construct my dependency tree in the Startup class explicitly and don't rely on something else that I don't control to resolve dependencies for me.

There's was an issue dotnet/extensions#615 for using ILogger without the container and it is fixed for .NET Core 3. I would expect that all other factories would follow the same approach and expose a way to instantiate factories without using the container, at least for the reason of consistency.

You are absolutely right that dependency injection is not everything I need. Sometimes dependency injection is more cumbersome and increases my workload. It seems useful but it is meaningless, 666

@sln162
Copy link

sln162 commented Oct 7, 2019

Is it possible to use the app.ApplicationServices.GetService() in the Configure method to get the IHttpClientFactory, then assign it to public static, and then globally.
Using this, I wrote an HttpHelper, which is just a static IHttpClientFactory, which is currently used without problems.
Do not know if there are hidden dangers, or will this follow-up support?
@rynowak

@rynowak
Copy link
Member

rynowak commented Nov 24, 2019

Is it possible to use the app.ApplicationServices.GetService() in the Configure method to get the IHttpClientFactory, then assign it to public static, and then globally.

I'm sure you can do this, and I'm sure that the code will work.

However you're baking in a dependency on a global static. Do you really want to do that? Will you ever want to test any of that code?

@sln162
Copy link

sln162 commented Nov 25, 2019

Is it possible to use the app.ApplicationServices.GetService() in the Configure method to get the IHttpClientFactory, then assign it to public static, and then globally.

I'm sure you can do this, and I'm sure that the code will work.

However you're baking in a dependency on a global static. Do you really want to do that? Will you ever want to test any of that code?

Thank you for your help. Our code is converted from asp.net. Not only httpclient needs to be called in the controller, but also in many classes. It's not easy to rely on the injection method, so I made a global static, maybe I didn't find a better way.

@MelbourneDeveloper
Copy link

Which NuGet, assembly and namespace is this mythical HttpClientFactory class located in? I can't find it anywhere...

@rynowak
Copy link
Member

rynowak commented Jan 6, 2020

package API

@voroninp
Copy link
Contributor

voroninp commented Jan 6, 2020

@davidfowl Why not to extract IHttpClientFactory interface as a separate NuGet package which won't have all ASP.NET Core dependencies?
Maybe even containing some lame default implementation of the factory which just reuses same SocketsHttpHandler per client name?

@rynowak
Copy link
Member

rynowak commented Jan 6, 2020

See: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1#alternatives-to-ihttpclientfactory

@voroninp
Copy link
Contributor

voroninp commented Jan 6, 2020

@rynowak I don’t need ‘an alternative’, I just want reusable abstraction, so my library works nice and has stable interface both in the context of ASP.NET Core and in a stand-alone application.

@bmwhite20
Copy link

Hi @rynowak,

Let me question you why SocketsHttpHandler doesn't target netstandard2.1?

@rynowak
Copy link
Member

rynowak commented Jan 16, 2020

SocketsHttpHandler is part of .NET Core, I'm not sure if there's a technical reason why it's not in netstandard2.1. I'd suggest asking at dotnet/standard or dotnet/runtime.

@MelbourneDeveloper
Copy link

@rynowak we're seeing .NET Core racing ahead without waiting for other platforms to catch up. This is dangerous and devalues .NET Standard. Xamarin and UWP both need the same APIs and further bifurcation is going to lead to more fragmentation of the .NET ecosystem.

@arnath
Copy link

arnath commented Feb 10, 2020

@rynowak Is there a recommended value for SocketsHttpHandler.PooledConnectionTimeout?

@rynowak
Copy link
Member

rynowak commented Feb 10, 2020

It depend on your scenario. I'd suggest starting with a value like 15 minutes and if that gives you good results, leave it.

@yanxiaodi
Copy link

yanxiaodi commented Mar 21, 2020

The tight coupling between the new IHttpClientFactory and Microsoft.Extensions.DependencyInjection is not a good design because it is trying to fix a defect by another defect. Now it is easy to use for ASP .NET Core apps but we are confused regarding how to use it in other apps, eg, Xamarin, WPF, etc. There are lots of DI tools in the world. I think it would be better to allow developers to freely use IHttpClientFactory without any specific DI tools.

Currently I would try to get the instance of IHttpClientFactory and register it again in another DI tool - which is ugly.

@MelbourneDeveloper
Copy link

MelbourneDeveloper commented Mar 21, 2020

@yanxiaodi . Exactly.

All that needs to be done is to give DefaultHttpClientFactory a public constructor as far as I can tell. To not give it a public constructor seems like bias against other IoC containers.

@bugproof
Copy link

bugproof commented Mar 21, 2020

@rynowak does configuring SocketsHttpHandler.PooledConnectionTimeout solve DNS issue as mentioned here https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net-core ?

I want exactly all benefits of HttpClientFactory but without DI in a normal console app (.NET Core 3.1).

@changhuixu
Copy link

I am curious about how does Azure key vault work when we add it as a configuration provider during the ConfigureAppConfiguration stage.

Azure SDK has a concept, HttpPipeline, which represents a primitive for sending HTTP requests and receiving responses.
https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs

Is the HttpPipeline a sibling of the HttpClient? Does .NET have something similar to the HttpPipeline that is primitive and can be used during the generic host build stage?

@davidfowl
Copy link
Member

It is a sibling. The AzureSDK team built their own primitive for outgoing http requests. The equivalent in the BCL is a MessageHandler

@arnath
Copy link

arnath commented Mar 31, 2020

For what it's worth, I wrote my own version of this that implements @rynowak 's recommended behavior above for .NET Core and .NET Standard. It doesn't use the exact interface because it's only available if you pull in a ton of ASP.NET dependencies but is pretty easy to use and I'm happy to make changes if needed.

https://github.com/arnath/standalone-httpclientfactory

@rduser
Copy link

rduser commented Apr 1, 2020

For what it's worth, I wrote my own version of this that implements @rynowak 's recommended behavior above for .NET Core and .NET Standard. It doesn't use the exact interface because it's only available if you pull in a ton of ASP.NET dependencies but is pretty easy to use and I'm happy to make changes if needed.

https://github.com/arnath/standalone-httpclientfactory

@rynowak , @davidfowl : For targeting .Net framework, is this a good solution to use under heavy load? What happens when the ServicePoint.ConnectionLeaseTimeout expires?

@ghost ghost closed this as completed Mar 30, 2021
@yanxiaodi
Copy link

This issue should not be closed. I don't see the proper solution after 2 years.

@Gladskih
Copy link

Gladskih commented Apr 1, 2021

That WebRequest-HttpWebRequest / HttpClient problem makes me think I've chosen wrong platform to learn and work with 10 years ago and all my life gone wrong.

@daiplusplus
Copy link

@rynowak please re-open this issue, @msftbot closed this when it shouldn't have.

@natalie-o-perret
Copy link

natalie-o-perret commented Apr 26, 2021

@bugproof
@yanxiaodi
@silkfire
@Gladskih
@Jehoel

According to the official Microsoft Docs, here:

Alternatives to IHttpClientFactory

Using IHttpClientFactory in a DI-enabled app avoids:

  • Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • The SocketsHttpHandler shares connections across HttpClient instances. This sharing prevents socket exhaustion.
  • The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

You can use HttpClient without relying on HttpClientFactory and DI, by using SocketsHttpHandler:

// Used throughout your application (i.e. app lifetime)
using var httpSocketHandler = new SocketsHttpHandler();
using var httpClient = new HttpClient(httpSocketHandler);
using var response = await httpClient.GetAsync(@"https://www.google.com");

@ericsampson
Copy link

@mary-perret-1986 that code snippet would need to be tweaked slightly to achieve the stated goal.

@natalie-o-perret
Copy link

@mary-perret-1986 that code snippet would need to be tweaked slightly to achieve the stated goal.

@ericsampson Would you mind elaborating on this?

@daiplusplus
Copy link

daiplusplus commented Apr 26, 2021

@mary-perret-1986 SocketsHttpHandler is not available in .NET Standard: it's only available in .NET Core 2.1 or later. If we're making redistributable libraries that use HttpClient then that makes things difficult - or if we're targeting .NET Framework then we're SOL. What solution would you suggest in that case?

@natalie-o-perret
Copy link

@mary-perret-1986 SocketsHttpHandler is not available in .NET Standard: it's only available in .NET Core 2.1 or later. If we're making redistributable libraries that use HttpClient then that makes things difficult - or if we're targeting .NET Framework then we're SOL. What solution would you suggest in that case?

As unrealistic as it may sound, I would suggest you to backport whatever you can to make it compliant with the version of the framework you're forced to use.

(I thought we were talking about .NET Core 3.x and .NET 5+, hence the reason I didn't really get why there was an issue for you).

@silkfire
Copy link

@mary-perret-1986 Doesn't it make more sense to make the default empty constructor of SocketsHttpHandler either private or internal and the only way to access the class for consumers be through a static SocketsHttpHandler.Instance? This would make sure that only one instance is ever created through the lifetime of your app.

@bugproof
Copy link

bugproof commented May 14, 2021

@mary-perret-1986 that code snippet would need to be tweaked slightly to achieve the stated goal.

@ericsampson Would you mind elaborating on this?

See? There's always something about HttpClient that will surprise you. You can't just use it with confidence. There should be only 1 instance of HttpClient in your app but then what's the point of BaseAddress? It's badly designed to be used as a singleton. HttpClientFactory solves these problems for you but it only works with DI because it's internal.

Ideally, the constructor of HttpClient should be internal or private so people will never ever instantiate it directly but through HttpClientFactory to promote good design but that would break many existing HttpClient wrappers. It's too late to fix the API I guess.

@daiplusplus
Copy link

daiplusplus commented May 14, 2021

@bugproof

There should be only 1 instance of HttpClient in your app but then what's the point of BaseAddress?

No-one is saying that HttpClient should be an application-wide singleton. I think you're misinterpreting @rynowak's advice (repeated below; the bold-for-relevance is mine)

  • If you are on .NET Core - you should use a single HttpClient directly and set SocketsHttpHandler.PooledConnectionTimeout here to an appropriate value.
  • If you are on .NET Framework - you should use a single HttpClient and use ServicePoint to configure the similar settings.

My understanding is Ryan meant each separate consumer of a HttpClient should have its own single instance - not that there should be an application-wide singleton. This makes sense given that HttpClient is mutable, and generally speaking instances of mutable classes should not be shared by unrelated consumers (also, I wish .NET's metadata system had C++-style const-correctness annotations so we could determine mutability through intellisense and without needing to fire-up ILSpy...)

@silkfire
Copy link

silkfire commented May 14, 2021

@Jehoel @stevejgordon This article mentions that SocketsHttpHandler handles connection cycling and lifetime management automatically just like DefaultHttpClientFactory does, but having peeked at the source code I can't see anything static residing on the class itself. So two instances wouldn't be sharing data between each other.

Sigh, I just wish Microsoft could make the DefaultHttpClientFactory API public.

@davidfowl
Copy link
Member

I don't understand why you think you need it. Are you managing different settings per handler? If not then you don't need it.

@silkfire
Copy link

I would believe that creating multiple HttpClients - one for every consuming service - would each use their own instance of SocketsHttpHandler, possibly leading to socket exhaustion. They would use their own respective pool, if I've understood this correctly. What I'm trying to say is I don't want apps to always have keep track of a singleton SocketsHttpHandler.

@davidfowl
Copy link
Member

Then use the same singleton instance?

@silkfire
Copy link

silkfire commented May 14, 2021

If this is how SocketsHttpHandler is to be used I'd rather it be a static instance than a singleton...

@davidfowl
Copy link
Member

Sure store it in your own static. There are obviously much more advanced use cases but the default should be a single instance used by your app until further notice

@JohnNilsson
Copy link

Just storing a singleton SocketsHttpHandler would mean a shared cookie container by default no?

Which is the kind of foot gun that leads to security issues when sessions are shared between clients unexpectedly.

@davidfowl
Copy link
Member

I believe cookies is off by default, so you would be opting into that footgun. Also I hope your API are using bearer tokens (though that's a separate discussion).

@silkfire
Copy link

So cookies are shared between different HttpClients if they use the same message handler?

@davidfowl
Copy link
Member

The cookie container is per handler instance. Like I said before, if you need to vary settings or state per handler then you need to manage multiple http clients. Otherwise use a single one

@ericsampson
Copy link

ericsampson commented May 14, 2021

@mary-perret-1986 that code snippet would need to be tweaked slightly to achieve the stated goal.

@ericsampson Would you mind elaborating on this?

The SocketsHttpHandler has a default connection lifetime of infinite, so you need to specify an explicit shorter lifetime if you want to achieve the goals that you mentioned of respecting callee DNS changes. This is mentioned in the text that you quoted : )
This also might be useful for you @silkfire , because it's only handled 'automatically' by SocketsHttpHandler if you configure it correctly.

The default value for this param can't be changed unfortunately, due to backwards compatiblity requirements.

using var httpSocketHandler = new SocketsHttpHandler(){ PooledConnectionLifetime = TimeSpan.FromMinutes(3) };
...

@silkfire
Copy link

silkfire commented May 15, 2021

The cookie container is per handler instance. Like I said before, if you need to vary settings or state per handler then you need to manage multiple http clients. Otherwise use a single one

@davidfowl And if I have varying settings on the handler? Would having multiple SocketsHttpHandler instances prevent me from gaining the benefits of any 'global' connection lifetime management then I presume?

@davidfowl
Copy link
Member

@davidfowl And if I have varying settings on the handler? Would having multiple SocketsHttpHandler instances prevent me from gaining the benefits of any 'global' connection lifetime management then I presume?

The connection management is coupled to the SocketsHttpHandler instance, so many instances means you'll end up with connection management spread across each instance. I think there are only a couple of settings that would force the creation of multiple handlers (that and a couple of scenarios). I suspect this is a more the advanced use-case.

@daiplusplus
Copy link

daiplusplus commented Jun 3, 2021

So if I understand things correctly...

Example 1: using-wrapped short-lived HttpClient from default constructor for a single request:

async Task MakeSingleRequestAsync()
{
    using( HttpClient httpClient = new HttpClient() )
    {
        using( HttpResponseMessage response = await httpClient.GetAsync( "https://www.example.com" ) )
        {
            // Do stuff
        }
    }
}

Example scenarios:

  • Probably everyone that has ever used HttpClient before reading that 2016 blog article.

API advantages and disadvantages:

  • Simple and straightforward.
  • The fact that HttpClient is IDisposable is a very strong hint that using() should be used.
  • You can easily mutate the HttpClient, such as setting DefaultRequestHeaders.

Platform-specific guidance:

  • .NET Framework: Don't do this - you'll have Socket Exhaustion.
  • .NET Core 1.0 through 5+: Don't do this - you'll have Socket Exhaustion.
    • HttpClient.Dispose() will dispose its private HttpClientHandler, which wraps its own private instance of SocketsHttpHandler, which owns its own HttpConnectionPoolManager, which is not shared between HttpClient instances.
  • .NET Standard: Don't do this
    • You don't know what the host platform will do - and both main platforms do it badly.

Example 2: Static or singleton long-life'd instance of HttpClient from default constructor, for many requests:

public static class Program
{
    private static readonly HttpClient httpClient = new HttpClient();
    
    public static async Task Main( String[] args )
    {
        using( HttpResponseMessage response = await httpClient.GetAsync( "https://www.example.com" ) )
        {
            // Do stuff
        }

        using( HttpResponseMessage response = await httpClient.GetAsync( "https://www.github.com" ) )
        {
            // Do stuff
        }
    }
}

API advantages and disadvantages:

  • As the HttpClient is static it cannot (or rather: shouldn't) be mutated for any request-specific reason.
    • So don't set DefaultRequestHeaders, for example.
    • In order to customize requests (cookies, etc!) you need to manually create your own HttpRequestMessage instances: you won't be able to use GetAsync or PostAsync. This is bad for usability!

Example scenarios:

  • Windows Service that makes periodic HTTP requests.
  • Long-lived background service object in ASP.NET 4.x via System.Web.Hosting.HostingEnvironment.RegisterObject( IRegisteredObject ).
  • Long-lived infinite-loop method invoked via System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem).
  • IHostedService in ASP.NET Core or .NET Core.

Platform-specific guidance:

  • .NET Framework: Don't do this without the ServicePoint workaround - your cached DNS results will go stale.
    • http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html
    • The workaround is to run this code below but for each distinct combination of Uri.Scheme, Uri.DnsSafeHost, Uri.Port that you make requests for - but you only need to do this once:
      • ServicePointManager.FindServicePoint( httpRequestMessage.RequestUri ).ConnectionLeaseTimeout = // Typically 1 minute. YMMV.
  • .NET Core 1.0 through 2.0: Don't do this - your cached DNS results will go stale.
  • .NET Core 2.1, 3.0, 3.1, 5.0: This is fine Don't do this - your cached DNS results may go stale.
  • .NET Standard: Don't do this - you don't know what the host platform will do.

.NET Framework-specific workaround example using extension methods:

public static class Extensions
{
    private readonly HashSet<String> _servicePointHistory = new HashSet<String>( StringComparer.OrdinalIgnoreCase );

    public static Uri EnsureServicePointSafety( this String url )
    {
        Uri uri = new Uri( url );
        
        String historyKey = uri.Scheme + ":" + uri.DnsSafeHost + ":" + uri.Port.ToString( CultureInfo.InvariantCulture );
        if( _servicePointHistory.Add( historyKey ) )
        {
            const Int32 ONE_MINUTE = 60 * 1000;
            ServicePointManager.FindServicePoint( httpRequestMessage.RequestUri ).ConnectionLeaseTimeout = ONE_MINUTE;
        }

        return uri;
    }
}

public static class Program
{
    private static readonly HttpClient httpClient = new HttpClient();
    
    public static async Task Main( String[] args )
    {
        using( HttpResponseMessage response = await httpClient.GetAsync( "https://www.example.com".EnsureServicePointSafety() ) )
        {
            // Do stuff
        }

        using( HttpResponseMessage response = await httpClient.GetAsync( "https://www.github.com".EnsureServicePointSafety() ) )
        {
            // Do stuff
        }
    }
}

Example 3: Static or singleton long-life'd instance of HttpClientHandler or SocketsHttpHandler, for many requests, with a short-lived HttpClient with disposeHandler: false:

public static class Program
{
#if DOTNET_FRAMEWORK
    private static readonly HttpClientHandler _httpClientHandler = CreateHttpClientHandler();
#elif DOTNET_CORE
    private static readonly SocketsHttpHandler _httpClientHandler = CreateSocketsHttpHandler();
#else
    #error you've fallen for one of the two classic blunders! The first being never get involved in a land war in Asia but only slightly lesser known: never try to use HttpClient in a .NET Standard project! HAHAHAHAHAHAHA *dies*
#endif
    
    public static async Task Main( String[] args )
    {
        using( HttpClient httpClient = new HttpClient( _httpClientHandler, disposeHandler: false ) )
        {
            httpClient.DefaultRequestHeaders.UserAgent.Add( _myProductInfo );
            using( HttpResponseMessage response = await httpClient.GetAsync( "https://www.example.com" ) )
            {
                // Do stuff
            }
        }

        using( HttpClient httpClient = new HttpClient( _httpClientHandler, disposeHandler: false ) )
        using( HttpResponseMessage response = await httpClient.GetAsync( "https://www.github.com" ) )
        {
            // Do stuff
        }
    }
}

API advantages and disadvantages:

  • As the HttpClientHandler is exposed we can set CookieContainer, AutomaticDecompression, and AllowAutoRedirect which we normally cannot do.
    • But when doing so we have to be careful not to share the HttpClientHandler.
      • But if we don't share the HttpClientHandler we'll end up creating excessive connections and may encounter socket exhaustion, even in .NET Core 5 because SocketsHttpHandler doesn't share its connection pool.
  • This has the advantage of letting you mutate HttpClient provided the HttpClient is not shared by other types or methods that have preconditions on the contents of DefaultRequestHeaders etc.

Example scenarios:

  • Same as Example 2.

Platform-specific guidance:

  • Same as Example 2.
  • .NET Core 2.1, 3.0, 3.1, 5.0: This approach is OK, but you still need to set PooledConnectionLifetime:

.NET Core 2.1+ -specific workaround for PooledConnectionLifetime:

public static class Program
{
    private static readonly SocketsHttpHandler _httpClientHandler = new SocketsHttpHandler()
    {
        PooledConnectionLifetime = TimeSpan.FromMinutes(1)
    };
   
    public static async Task Main( String[] args )
    {
        using( HttpClient httpClient = new HttpClient( _httpClientHandler, disposeHandler: false ) )
        {
            httpClient.DefaultRequestHeaders.UserAgent.Add( _myProductInfo );
            using( HttpResponseMessage response = await httpClient.GetAsync( "https://www.example.com" ) )
            {
                // Do stuff
            }
        }

        using( HttpClient httpClient = new HttpClient( _httpClientHandler, disposeHandler: false ) )
        using( HttpResponseMessage response = await httpClient.GetAsync( "https://www.github.com" ) )
        {
            // Do stuff
        }
    }
}

Example 4: You're creating a redistributable web-service client library class:

public class SilkRoadClient : ISilkRoadClient
{
    // TODO: Should this class own its own HttpClientHandler?
    // TODO: Should this class own its own SocketsHttpHandler? What if it's .NET Framework or .NET Standard where it's unavailable?
    // TODO: Should this class own its own HttpClient? Who should be responsible for creating it? Should it be mutable?
    // TODO: Should this type be disposable if it owns/implements any of the above?
    // TODO: If this class does implement IDisposable, should `ISilkRoadClient` also extend `IDisposable`?
    // TODO: What should the lifetime semantics of this type be? Transient? Long-lived? "Medium-lived" (e.g. end-user session length)? What if it's a singleton?

    public SilkRoadClient( /* what goes here? */ )
    {

    }

    public async Task<SubstancesInvoice> BuyMoreAdderallOnTheDarkWebSoICanPutUpWithEverIncreasingHttpClientRevelationsAsync( Int32 qty, Int32 strengthMG, String bitcoinPrivateKey )
    {
        // * Should this method create its own HttpClient?
        // * Should this method dispose its own HttpClient?
        // * How should this method add common request headers and/or set cookies and handle Set-Cookie headers in response?
    }
}

Welcome to heck. Figuring out how to handle HttpClient when you don't own the entrypoint or host program that will run your code is very difficult.

I've added comments to the above code that point out all of the unanswered questions I still have regarding using HttpClient in a redistributable library where IHttpClientFactory isn't necessarily available - this is the current void in the documentation and guidance for HttpClient.

I note that the simplest approach is still to just take a dependency on IHttpClientFactory - but that comes with the massive cost of getting complaints from your users who now have no way to quickly and easily fire-up an instance of your client class.

Example 5: You're creating a redistributable web-service client library class - and you use OAuth2 / OIDC and need to handle automatic access_token renewals

Oh gravy...

This problem is now twice as hard as Example 4 because now you need to consider the lifetime of the separate HttpClient or HttpClientHandler when making the separate requests to the IdP service (which will be at a completely different Origin).

...and if your access_token or renewer state is shared by multiple other services then what?

TL;DR: I can't use IHttpClientFactory but I just want a safe and reliable HttpClient that supports CookieContainer and works in .NET Standard 2.0:

  • You're SOL, sorry - as is my understanding of the situation at present.

@ericsampson
Copy link

@Jehoel, FWIW if you want to see how someone has handled these issues, you can take a look at the Azure SDK. IIRC they've dealt with this.

I'm definitely not saying that it shouldn't be easier OOTB, but at least as a reference…

@dotnet dotnet locked as resolved and limited conversation to collaborators Jul 3, 2021
@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Jun 2, 2023
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions feature-httpclientfactory Includes: HttpClientFactory (some bugs also in Extensions repo) question
Projects
None yet
Development

No branches or pull requests