dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.15k stars 9.92k forks source link

Using HttpClientFactory without dependency injection #28385

Closed poke closed 3 years ago

poke commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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

poke commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

Hey everyone

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

alexeyzimarev commented 5 years ago

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 https://github.com/aspnet/Extensions/issues/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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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

rynowak commented 4 years ago

package API

voroninp commented 4 years ago

@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 commented 4 years ago

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

voroninp commented 4 years ago

@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 commented 4 years ago

Hi @rynowak,

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

rynowak commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

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

rynowak commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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

arnath commented 4 years ago

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 commented 4 years ago

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?

rynowak commented 4 years ago

heavy load

Heavy load is a very contextual term. What does it mean for your use case?

From the docs

When the ConnectionLeaseTimeout property is set to a value other than -1, and after the specified time elapses, an active ServicePoint connection is closed after servicing a request by setting KeepAlive to false in that request.

@davidsh may be able to say more.

davidsh commented 4 years ago

When the ConnectionLeaseTimeout property is set to a value other than -1, and after the specified time elapses, an active ServicePoint connection is closed after servicing a request by setting KeepAlive to false in that request.

Keep in mind the ServicePointManager and ServicePoint objects are no-op in .NET Core. So, using these properties only affect .NET Framework applications.

But the docs describe the behavior of the ConnectionLeaseTimeout correctly.

ie-zero commented 4 years ago

Today, I was refactoring a legacy piece of code which could be improved using IHttpClientFactory. However, as the class has a default constructor, a default implementation of the IHttpClientFactory will be useful.

RustyF commented 4 years ago

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

For ASP.NET Core 3+ the Autofac configuration is automatic and simpler. See https://autofaccn.readthedocs.io/en/stable/integration/aspnetcore.html#asp-net-core-3-0-and-generic-hosting

JHeLiu commented 4 years ago

Have you considered projects without DI?

  1. For many projects, we do not need to use DI, for the following reasons
  2. Added interface layer (unnecessary)
  3. The project is not very large, and it will compile in half a minute or a few minutes (DI cannot play its advantages).
  4. The functions of the project do not need dynamic loading and dynamic installation of business functions Obviously, there are many such projects, and they will continue to be written with reference dependency. Many such projects are maintained by only one or a few people, so DI may increase the development cost and maintenance cost. So we're hoping for a simpler way to use it, like we're using classic layered development, which encapsulates Shared things in a DLL.It might expose a little bit of the configuration and it might look something like this in http.cs
 void Get(string url, Dictionary<string, string> Headers = null, int timeout = 30)
        {
            var client = new HttpClient();

            if (Headers != null && Headers.Count > 0)
            {
                foreach (var item in Headers)
                {
                    client.DefaultRequestHeaders.TryAddWithoutValidation(item.Key, item.Value);
                }
            }

            client.Timeout = TimeSpan.FromSeconds(timeout);

            client.GetAsync(url);
        }

This is a simple example, we know there are static and DI can solve the problem, but I really want to use static to achieve this, because I want to use static, will it automatically manage the release of the problem??As far as I know, the static should not be freed, I guess, if the object can't be freed it can, I like to know TCP will it automatically close?Let's say I do it statically

davidfowl commented 4 years ago

The factory won’t help you with your static method. Just use a singleton http client or write and extension method. I’m not sure what most of those points have to do with the client factory. It’s unclear why you need to use it when your scenario is as simplistic as the sample code you wrote

JHeLiu commented 4 years ago

@davidfowl Hello, here's the thing. When we use static, we can't share the parameters, for example, Timeout can't be set, because this project might request multiple webapi under the domain name, we are considering less code implementation, which obviously doesn't seem to work, like I have a.com b.com c.com XXXXX Webapi, I seem to have to have them statically stored separately, otherwise they will report errors when setting Shared parameters such as Timeout. I've been looking for a better solution, but of course we're doing it statically

davidfowl commented 4 years ago

What’s the exact code do you want to write? What would this type solve for you and how would it do that?

JHeLiu commented 4 years ago

@davidfowl

        static void Main(string[] args)
        {
            var url1 = "http//a.com/use/a";
            var url2 = "http//b.com/use/b";
            var url3 = "http//c.com/use/c";

            Get(url1, null, 10);
            Get(url2, null, 15);
            Get(url3, null, 30);

        }

        static HttpClient client = new HttpClient();
        static void Get(string url, Dictionary<string, string> Headers = null, int timeout = 30)
        {
            if (Headers != null && Headers.Count > 0)
            {
                foreach (var item in Headers)
                {
                    client.DefaultRequestHeaders.TryAddWithoutValidation(item.Key, item.Value);
                }
            }
            client.Timeout = TimeSpan.FromSeconds(timeout);
            client.GetAsync(url);
        }
}

For example, a public method can run multiple web apis, including Settings

davidfowl commented 4 years ago

I’m not seeing how you wound use the factory. Could you also write a sample of that

JHeLiu commented 4 years ago

@davidfowl

namespace ConsoleApp1
{
    class Program
    {
        static HttpClient httpClient = new HttpClient();
        static void Main(string[] args)
        {
            var msUrl = "https://docs.microsoft.com/en-us/dotnet/core/";
            var edgeUrl = "https://www.microsoft.com/zh-cn/edge";

            var http = new UseHttpClient();
            var rs = http.Get(msUrl);

            //I can't change the timeout when I have other requests
            var edgeRs = http.Get(edgeUrl);

            Console.WriteLine(rs);
        }
    }

    /// <summary>
    /// HttpClient 
    /// </summary>
    public class UseHttpClient
    {

      static HttpClient httpClient = new HttpClient();

        /// <summary>
        /// get 
        /// </summary>
        /// <param name="url"></param>
        /// <param name="timeout"></param>
        /// <param name="Headers"></param>
        /// <returns></returns>
        public string Get(string url, int timeout = 30, Dictionary<string, string> Headers = null)
        {
            if (Headers != null && Headers.Count > 0)
            {
                foreach (var item in Headers)
                {
                    httpClient.DefaultRequestHeaders.TryAddWithoutValidation(item.Key, item.Value);
                }
            }
            httpClient.Timeout = TimeSpan.FromSeconds(timeout);
            return httpClient.GetAsync(url).Result.Content.ReadAsStringAsync().Result;
        }
    }
}

You can try running this, I think the public method of this class can access any webapi.And you can set related parameters, such as timeouts

davidfowl commented 4 years ago

Use a cancellation token to have a timeout on the request itself. Is that all you’re trying to solve?

JHeLiu commented 4 years ago

@davidfowl Yes, I want to be able to set a timeout and also close the connection automatically

davidfowl commented 4 years ago

Close the connection? Are you trying to use http 1.0? You can use a cancellation token for timeouts instead of setting the property on the client

JHeLiu commented 4 years ago

@davidfowl It doesn't seem like it can be done. Some of the interface timeouts are longer, like 15 seconds, and some are shorter

davidfowl commented 4 years ago

I’m not sure what you mean. What can’t be done?

JHeLiu commented 4 years ago

@davidfowl The connection can only be broken on the client side

davidfowl commented 4 years ago

That’s not true. But I’m really confused as to what you’re trying to do. I think I gave you answers:

JHeLiu commented 4 years ago

@davidfowl I want to be able to dynamically set the timeout, to share one method and one HttpClient, because using does not release properly, so I want to use the singleton HttpClient on a static basis to dynamically set the timeout, but this one doesn't seem to be set

davidfowl commented 4 years ago

You can if you use a cancellation token for the call instead of setting the timeout property