I've read that HttpMessageHandlers are recycled every 2 minutes, but I'm not sure if a new one is assigned to an existing HttpClient?
I've tested it out by using SetHandlerLifetime(TimeSpan.FromSeconds(5)); and even after 2 minutes with countless requests, the httpClient is continuing to work, which is a good sign?
Does that mean that I have no cause for concern with DNS changes/socket exhaustion?
Inside ConfigureServices method:
var myOptions = app.ApplicationServices.GetService<IOptionsMonitor<MyOptions>>();
HttpClient httpClient = app.ApplicationServices.GetService<IHttpClientFactory>().CreateClient();
MyStaticObject.Configure(myOptions, httpClient);
EDIT: Added some sample code.
There are a few things to look at here, with the first being does MyStaticObject actually need to be static? If it does, I would recommend instead registering it as a Singleton so that you can still leverage dependency injection. Once you have done that, you can register IHttpClientFactory and use it from your code. Your ConfigureServices method may end up looking something like this
public void ConfigureServices(IServiceCollection services)
{
//The base extension method registers IHttpClientFactory
services.AddHttpClient();
services.AddSingleton<IMySingletonObject, MySingletonObject>();
}
Then in your consuming class, MySingletonObject in this case, you would configure it as such
public class MySingletonObject
{
private readonly IHttpClientFactory _clientFactory;
public MySingletonObject(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task SomeMethodThatUsesTheClient()
{
var client = _clientFactory.CreateClient();
//use the client
}
}
The reason for this is that IHttpClientFactory handles the lifetime and pool concerns for us. Per the docs:
Manages the pooling and lifetime of underlying HttpClientMessageHandler instances. Automatic management avoids common DNS (Domain Name System) problems that occur when manually managing HttpClient lifetimes.
This happens when you make the CreateClient call, so you want to do this inside the code using the client, as opposed to on startup of your application.
As a side note, if you do not need this class to be a singleton at all, you can use the extenion services.AddHttpClient<IMyClass, MyClass>() and inject an HttpClient directly into the class. The DI container will handle getting a client from the factory behind the scenes for you.
public static IServiceCollection AddApiClient(this IServiceCollection services,
Action<RgCommunicationClientOptions> action)
{
services.AddHttpClient<ISomeApiClient, SomeApiClient>()
.AddPolicyHandler(GetRetryPolicy())
.SetHandlerLifetime(TimeSpan.FromMinutes(4));
services.AddOptions();
services.Configure(action);
return services;
}
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
//.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
services.AddApiClient(options => options.BaseAddress = configuration.GetSection("ExternalServices:SomeApi")["Url"]);
You can inject HttpClient like above in the class via dependency injection. I have used Poly extension to add RetryPolicy. Also you can set the LifetTime of the handler by using SetHaandlerLifeTime. This way you can individually configure client for each client. In the logs you can see that httpHandler expires after 4 mins for the respective call. I have used the extension method to pass the options in the method, getting values via app settings.
Hope this helps.
Blockquote
Related
Grpc.Net.ClientFactory package provides gRPC integration with the IHttpClientFactory and it comes with the convinient extension method used to register gRPC clients: IServiceCollection.AddGrpcClient<TClient>().
The problem is that this method registers TClient with the transient lifetime, which is, in turn, means that it's imposible to inject it into the singleton service. And looks like there is no possibility to configure it somehow (at least the AddGrpcClient source code has nothing on that).
So the question is: how to correclty inject the gRPC client to the singleton service and benefit from the IHttpClientFactory integration at the same time? Should I inject some client factory instead?
How about using named Grpc client to register the client, using like this.
// Register on the Startup.cs
services
.AddGrpcClient<Catalog.CatalogClient>("Catalog", o =>
{
o.Address = new Uri("https://localhost:5001");
});
// Create your service
public class OrderingService : IOrderingService
{
private readonly Catalog.CatalogClient _client;
public OrderingService(GrpcClientFactory grpcClientFactory)
{
_client = grpcClientFactory.CreateClient<Catalog.CatalogClient>("Catalog");
}
}
// Register the service as singleton at Startup.cs
service.AddSingleton<IOrderingService, OrderingService>();
// now _client instance is persist as long as you doesn't do anything weird with it
The grpc client might be a new instance each time (transient), but the underlying HttpClientMessageHandler "is managed".
I see no problems injecting a transient instance into a singleton service.
I've created a C# microservice which offers several different (but related) functions.
I am now creating a C# Nuget package for a client which will help other C# microservices to leverage this microservice. This will include a Service Collection Extension class to facilitate adding the client to those microservices.
In the interest of separating concerns, within the client, I've separated the functionality into three classes:
SalesforceCacheQuerier
SalesforceCacheSyncDataManipulator
SalesforceCacheAsyncDataManipulator
Each of these need to call out to the same server.
As a niave first implementation, I've composed this method:
public static IServiceCollection AddSalesforceClients(this IServiceCollection services)
{
services.AddTransient<SalesforceCacheAuthenticationHandler>();
ConfigureClient(services.AddHttpClient<ISalesforceCacheQuerier, SalesforceCacheQuerier>());
ConfigureClient(services.AddHttpClient<ISalesforceCacheSyncDataManipulator, SalesforceCacheSyncDataManipulator>());
ConfigureClient(services.AddHttpClient<ISalesforceCacheAsyncDataManipulator, SalesforceCacheAsyncDataManipulator>());
return services;
}
private static IHttpClientBuilder? ConfigureClient(IHttpClientBuilder? clientBuilder)
=> clientBuilder.ConfigureHttpClient(ConfigureClient)
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler())
.AddHttpMessageHandler<SalesforceCacheAuthenticationHandler>();
private static void ConfigureClient(IServiceProvider provider, HttpClient client)
{
SalesforceCacheSettings? settings = provider.GetRequiredService<SalesforceCacheSettings>();
client.BaseAddress = new Uri(settings.BaseUrl, settings.ApiEndpoint);
client.DefaultRequestHeaders.ExpectContinue = true;
}
However, this generates three separate HttpClients and triples the traffic for the "Identity Server" used to provide Jwt tokens.
How can I refactor this to create and reuse only a single HttpClient?
You can use HttpClientFactory and inject it in ConfigureServices
something like services.AddHttpClient();
Later wherever you need client object just give IHttpClientFactory httpClientFactory in the constructor and you can have access to client object by just asking the factory to create a client.
HttpClient = HttpClientFactory.CreateClient(); in this fashion.
In case if you want to hold the authentication for the clients and reuse it. I would suggest to maintain a dictionary for each request type.
var HttpClients = new Dictionary<string, HttpClient>();
HttpClients.Add(SalesforceCacheQuerierKey, SalesforceCacheQuerierClient);
//assuming all the auth related headers are added to this client object //SalesforceCacheQuerierClient
and pass these from startup level.
What I needed was something like this:
services.AddHttpClient(SALESFORCE_CACHE_CLIENT, (provider, client) =>
{
SalesforceCacheClientSettings? settings = provider.GetRequiredService<SalesforceCacheClientSettings>();
client.BaseAddress = new Uri(settings.BaseUrl, settings.ApiEndpoint);
client.DefaultRequestHeaders.ExpectContinue = true;
})
.AddHttpMessageHandler<SalesforceCacheAuthenticationHandler>();
services.AddHttpClient<ISalesforceCacheQuerier, SalesforceCacheQuerier>(SALESFORCE_CACHE_CLIENT);
services.AddHttpClient<ISalesforceCacheSyncDataManipulator, SalesforceCacheSyncDataManipulator>(SALESFORCE_CACHE_CLIENT);
services.AddHttpClient<ISalesforceCacheAsyncDataManipulator, SalesforceCacheAsyncDataManipulator>(SALESFORCE_CACHE_CLIENT);
I've created a custom library which automatically sets up Polly policies for specific services which depend on HttpClient.
This is done using the IServiceCollection extension methods and the typed client approach. A simplified example:
public static IHttpClientBuilder SetUpFooServiceHttpClient(this IServiceCollection services)
{
return services
.AddHttpClient<FooService>()
.AddPolicyHandler(GetRetryPolicy());
}
The example service:
public class FooService
{
private readonly HttpClient _client;
// OPTION 1
public FooService(HttpClient httpClient)
{
_client = httpClient;
}
// OPTION 2
public FooService(IHttpClientFactory httpClientFactory)
{
_client = httpClientFactory.CreateClient(GetType().Name);
}
public void DoJob()
{
var test = _client.GetAsync("http://example.com");
}
}
Fetching the service from the DI container (this is from a test project):
var services = new ServiceCollection();
services.SetUpFooServiceHttpClient();
services.AddSingleton<FooService>();
var fooService = services
.BuildServiceProvider()
.GetRequiredService<FooService>();
// Perform test
fooService.DoJob();
Note: In this test project, I also add an extra mocked handler since I'm trying to mock http status responses, but the mocked handler being present or not is identical to the Polly policy being present or not, so I omitted the mocked handler from the example code.
Notice the two different constructors in FooService. Depending on which one I comment out and which one I leave in, I get different outcomes. All other code remains untouched.
Option 1, injecting the HttpClient directly, ignores all my configuration. I get a standard http client with no Polly policy handler.
Options 2, injecting the IHttpClientFactory and requesting the client using the current type name (i.e. FooService) respect my configuration. I get a custom http client which contains the Polly policy handler (and any other handlers I may have configured, e.g. mocked handlers in my test suite)
The absence/existence of the policy handler is confirmed in both cases using debug inspection.
According to all documentation I've found on the subject, both options should be equivalent, at least in regards to the constructed HttpClient that I obtain in the end. But that is not the case here.
The documentation I find specifies that HttpClient can be injected when using typed clients:
The MSDN documentation, specifically the "typed clients" section example.
This SO answer
This blog post
I'm using a typed client but injecting a HttpClient clearly doesn't work for me.
Why does injecting a HttpClient vs injecting IHttpClientFactory work differently in my case?
In effect, you have the following two registrations for your FooService class:
services.AddHttpClient<FooService>()
services.AddSingleton<FooService>();
Because of how the DI container works behind-the-scenes, the second registration overwrites the first. If you remove the second registration, the first will be used, and so your constructor with the HttpClient parameter will be invoked.
I have a scoped service:
public class GetLatestStatus:IGetLatestStatus{
private HttpClient _httpClient;
private readonly int _status;
public GetLatestStatus(HttpClient httpClient){
_httpClient = httpClient;
_date= GetStatusFromService();
}
public string GetStatus(){
return _status;
}
private string GetStatusFromService(){
Logger.Info($"Calling service...");
var request = new HttpGetRequest{Url = "http://some.service/get/status"};
var result = _httpClient.Get(request).Result;
return result.Status;
}
}
Here is how it is defined in the startup:
public virtual void ConfigureServices(IServiceCollection services){
services.AddScoped<IGetLatestStatus, GetLatestStatus>()
.AddHttpClient<IGetLatestStatus, GetLatestStatus>();
services.AddTransient<ISomeClass1, SomeClass1>();
services.AddTransient<ISomeClass2, SomeClass2>();
services.AddTransient<ISomeClass3, SomeClass3>();
}
It is being used by three transient classes.
The intent of this class is that _status is defined only once, when the request comes in. Then it is stored throughout the lifecycle of the request.
Instead, it seems that GetStatusFromService() is being called three times, one per transient class, when the request first comes in.
How do I make this class work the way I intended? I thought that defining something as a Scoped Service means that there's only one copy for the lifecycle of the request. Thank you all for the help!
TL:DR
It happens because you register GetLatestStatus like this after scoped registration .AddHttpClient<IGetLatestStatus, GetLatestStatus>();
So may create another class to store the status and register it as scoped. Then use the Http Configured service to reach the service from it
According to MSDN;
To configure the above structure, add HttpClientFactory in your application by installing the Microsoft.Extensions.Http NuGet package that includes the AddHttpClient() extension method for IServiceCollection. This extension method registers the DefaultHttpClientFactory to be used as a singleton for the interface IHttpClientFactory. It defines a transient configuration for the HttpMessageHandlerBuilder. This message handler (HttpMessageHandler object), taken from a pool, is used by the HttpClient returned from the factory.
Please check the link for more information https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
Everywhere I can see three main approaches to create clients (basic, named, typed) in DI, but I have found nowhere if to inject IHttpClientFactory or HttpClient (both possible).
Q1: What is the difference between injecting IHttpClientFactory or HttpClient please?
Q2: And if IHttpClientFactory is injected, should I use factory.CreateClient() for each call?
Summary
HttpClient can only be injected inside Typed clients
for other usages, you need IHttpClientFactory
In both scenarios, the lifetime of HttpClientMessageHandler is managed by the framework, so you are not worried about (incorrectly) disposing the HttpClients.
Examples
In order to directly inject HttpClient, you need to register a specific Typed service that will receive the client:
services.AddHttpClient<GithubClient>(c => c.BaseAddress = new System.Uri("https://api.github.com"));
Now we can inject that inside the typed GithubClient
public class GithubClient
{
public GithubClient(HttpClient client)
{
// client.BaseAddress is "https://api.github.com"
}
}
You can't inject the HttpClient inside AnotherClient, because it is not typed to AnotherClient
public class AnotherClient
{
public AnotherClient(HttpClient client)
{
// InvalidOperationException, can't resolve HttpClient
}
}
You can, however:
1. Inject the IHttpClientFactory and call CreateClient(). This client will have BaseAddress set to null.
2. Or configure AnotherClient as a different typed client with, for example, a different BaseAdress.
Update
Based on your comment, you are registering a Named client. It is still resolved from the IHttpClientFactory.CreateClient() method, but you need to pass the 'name' of the client
Registration
services.AddHttpClient("githubClient", c => c.BaseAddress = new System.Uri("https://api.github.com"));
Usage
// note that we inject IHttpClientFactory
public HomeController(IHttpClientFactory factory)
{
this.defaultClient = factory.CreateClient(); // BaseAddress: null
this.namedClient = factory.CreateClient("githubClient"); // BaseAddress: "https://api.github.com"
}
Sadly I cannot comment, but only Post an answer. Therefore I suggest you should check out the following Links:
https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
Regarding your Questions it more or Less boils down to this:
Q1 -> IHttpClientFactory handles the connection pools of HttpClient instances and this will help you regarding load and dispose problems as discribed in the links, if the HttpClient is used wrong.
Q2 -> yes you should use factory.create client according to microsoft docs