Best Practice for Use HttpClient - c#

I'm using HttpClient to make request to WebApi.
I have written this code
public async Task<string> ExecuteGetHttp(string url, Dictionary<string, string> headers = null)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(url);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (headers != null)
{
foreach (var header in headers)
{
client.DefaultRequestHeaders.Add(header.Key, header.Value);
}
}
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
Now I'm calling this method from my action.
public async Task<ActionResult> Index()
{
try
{
RestWebRequest RestWebRequest = new RestWebRequest();
Dictionary<string, string> headers = new Dictionary<string, string>();
headers.Add("Authorization", "bearer _AxE9GWUO__8iIGS8stK1GrXuCXuz0xJ8Ba_nR1W2AhhOWy9r98e2_YquUmjFsAv1RcI94ROKEbiEjFVGmoiqmUU7qB5_Rjw1Z3FWMtzEc8BeM60WuIuF2fx_Y2FNTE_6XRhXce75MNf4-i0HbygnClzqDdrdG_B0hK6u2H7rtpBFV0BYZIUqFuJpkg4Aus85P8_Rd2KTCC5o6mHPiGxRl_yGFFTTL4_GvSuBQH39RoMqNj94A84KlE0hm99Yk-8jY6AKdxGRoEhtW_Ddow9FKWiViSuetcegzs_YWiPMN6kBFhY401ON_M_aH067ciIu6nZ7TiIkD5GHgndMvF-dYt3nAD95uLaqX6t8MS-WS2E80h7_AuaN5JZMOEOJCUi7z3zWMD2MoSwDtiB644XdmQ5DcJUXy_lli3KKaXgArJzKj85BWTAQ8xGXz3PyVo6W8swRaY5ojfnPUmUibm4A2lkRUvu7mHLGExgZ9rOsW_BbCDJq6LlYHM1BnAQ_W6LAE5P-DxMNZj7PNmEP1LKptr2RWwYt17JPRdN27OcSvZZdam6YMlBW00Dz2T2dgWqv7LvKpVhMpOtjOSdMhDzWEcf6yqr4ldVUszCQrPfjfBBtUdN_5nqcpiWlPx3JTkx438i08Ni8ph3gDQQvl3YL5psDcdwh0-QtNjEAGvBdQCwABvkbUhnIQQo_vwA68ITg07sEYgCl7Sql5IV7bD_x-yrlHyaVNtCn9C4zVr5ALIfj0YCuCyF_l1Z1MTRE7nb");
var getCategories = await RestWebRequest.ExecuteGetHttp("http://localhost:53646/api/Job/GetAllCategories?isIncludeChild=true", headers);
}
catch (HttpRequestException ex)
{
return View();
}
return View();
}
Now It is said that HttpClient has been designed to be re-used for multiple calls.
How Can I use same httpClient object for multiple calls.
Let's suppose
First I'm calling
http://localhost:53646/api/Job/GetAllCategories?isIncludeChild=true
Now In same controller I have to call another Api with diffrent header and diffrent url.
http://localhost:53646/api/Job/category/10
Should I make the global object of HttpClient and Use the same object for all API calls.

The challenge in using just one HttpClient across your application is when you want to use different credentials or you try to vary the default headers for your requests (or anything in the HttpClientHandler passed in). In this case you will need a set of purpose specific HttpClients to re-use since using just one will be problematic.
I suggest creating a HttpClient per the "type" of request you wish to make and re-use those. E.g. one for each credential you need - and maybe if you have a few sets of default headers, one per each of those.
It can be a bit of a juggling act between the HttpClient properties (which are not thread safe) and need their own instance if being varied:
- BaseAddress
- DefaultRequestHeaders
- MaxResponseContentBufferSize
- Timeout
And what you can pass in to the "VERB" methods (get, put, post etc). For example, using HttpClient.PostAsync Method (String, HttpContent) you can specify your headers for the [HttpContent][3] (and not have to put them in the HttpClient DefaultHeaders).
All of the Async methods off the HttpClient are thread safe (PostAsync) etc.

Just because you can, doesn't mean you should.
You don't have to, but you can reuse the HttpClient, for example when you want to issue many HTTP requests in a tight loop. This saves a tiny fraction of time it takes to instantiate the object.
Your MVC controller is instantiated for every request. So it won't harm any significant amount of time to instantiate a HttpClient at the same time. Remember you're going to issue an HTTP request with it, which will take many orders more time than the instantiation ever will.
If you do insist you want to reuse one instance, because you have benchmarked it and evaluated the instantiation of HttpClient to be your greatest bottleneck, then you can take a look at dependency injection and inject a single instance into every controller that needs it.

in .net core you can do the same with HttpClientFactory something like this:
public interface IBuyService
{
Task<Buy> GetBuyItems();
}
public class BuyService: IBuyService
{
private readonly HttpClient _httpClient;
public BuyService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Buy> GetBuyItems()
{
var uri = "Uri";
var responseString = await _httpClient.GetStringAsync(uri);
var buy = JsonConvert.DeserializeObject<Buy>(responseString);
return buy;
}
}
ConfigureServices
services.AddHttpClient<IBuyService, BuyService>(client =>
{
client.BaseAddress = new Uri(Configuration["BaseUrl"]);
});
documentation and example at here and here

Related

Trouble posting more than once to webapi from Blazor component- "This instance has already started one or more requests." [duplicate]

I am creating an application in .Net Core 2.1 and I am using http client for web requests. The issue is I have to send parallel calls to save time and for that I am using Task.WhenAll() method but when I hit this method I get the error "This instance has already started one or more requests. Properties can only be modified before sending the first request" Previously I was using RestSharp and everything was fine but I want to use httpclient. Here is the code:
public async Task<User> AddUser(string email)
{
var url = "user/";
_client.BaseAddress = new Uri("https://myWeb.com/");
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(Constants."application/json"));
_client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var json = new {email = email }
var response = await _client.PostAsJsonAsync(url,json);
if (response .IsSuccessStatusCode)
{ ....
Here is the constructor:
private readonly HttpClient _httpClient;
public UserRepository(HttpClient httpClient)
{
_httpClient = httpClient;
}
Method calling:
var user1 = AddUser("user#user.com");
var user2 = AddUser("test#test.com");
await Task.WhenAll(user1, user2);
and here is the startup configuation:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
So what am I doing wrong? Do I need to change AddSingleton with AddTransient() or is there any other issue. One more question do I need to use _client.Dispose() after the response because the tutorial which I followed didn't use dispose method so I am little confused in that.
HttpClient.DefaultRequestHeaders (and BaseAddress) should only be set once, before you make any requests. HttpClient is only safe to use as a singleton if you don't modify it once it's in use.
Rather than setting DefaultRequestHeaders, set the headers on each HttpRequestMessage you are sending.
var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = new StringContent("{...}", Encoding.UTF8, "application/json");
var response = await _client.SendAsync(request, CancellationToken.None);
Replace "{...}" with your JSON.
Maybe my two cents will help someone.
I ran into this issue when refreshing the page when debugging the application.
I was using a singleton, but each refresh, it was trying to set the base address. So I just wrapped it in a check to see if the base address had already been set.
The issue for me was, it was trying to set the baseAddress, even though it was already set. You can't do this with a httpClient.
if (_httpClient.BaseAddress == null)
{
_httpClient.BaseAddress = new Uri(baseAddress);
}
The issue is caused by resetting BaseAddress and headers for the same instance of the httpclient.
I tried
if (_httpClient.BaseAddress == null)
but I am not keen on this.
In my opinion, a better soloution is to use the httpclientFactory. This will terminate and garbage collect the instance of the httpclient after its use.
private readonly IHttpClientFactory _httpClientFactory;
public Foo (IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public httpresponse Bar ()
{
_httpClient = _httpClientFactory.CreateClient(command.ClientId);
using var response = await _httpclient.PostAsync(uri,content);
return response;
// here as there is no more reference to the _httpclient, the garbage collector will clean
// up the _httpclient and release that instance. Next time the method is called a new
// instance of the _httpclient is created
}
It Works well when you add the request url and the headers at the message, rather than at the client. So better not to assign to BaseAddress Or the header DefaultRequestHeaders if you will use them for many requests.
HttpRequestMessage msg = new HttpRequestMessage {
Method = HttpMethod.Put,
RequestUri = new Uri(url),
Headers = httpRequestHeaders;
};
httpClient.SendAsync(msg);

Simple way to transfer cookies from current HttpContext into new created HttpClient request

I'm trying to wrap some api request
[Route("foo")]
public Task Foo()
{
using var http = new HttpClient();
return http.PostAsync(
Endpoint,
new FormUrlEncodedContent(new Dictionary<string, string>
{
{ ClientId, "ClientId" },
}),
CancellationToken.None)
.ConfigureAwait(false);
}
and getting issue with it. Cause is in cookies that uses by called endpoint.
Is there any way to transfer cookies from my current HttpContex into HttpClient Post call? I know, I can use CookieContainer, HttpClientHandler and pass all this stuff into HttpClient, but I would like use something more elegance.
Just grab the Cookie header from the incoming request and add to the outgoing one. Setting a header on an individual request will require creating an HttpRequestMessage explicitly and using HttpClient.SendAsync to send it, but that's fairly simple:
var outgoing = new HttpRequestMessage(HttpMethod.Post, uri);
outgoing.Content = new FormUrlEncodedContent(...);
if (Request.Headers.TryGetValue("Cookie", out var cookies))
{
outgoing.Headers.TryAddWithoutValidation("Cookie", cookies);
}
await http.SendAsync(outgoing);

"Pass through" controller action (gets and returns JSON) in .NET Core 3.1

Someone's probably done this before but I can't seem to formulate the question properly to find results. I want to make AJAX calls from a view, but I can't directly call the external API from javascript because there's a key that I can't expose. My idea is to have another controller action that I call from the page that calls the actual external REST API I want to get data from and just passes it on as a JSON. I see lots of examples of getting a JSON through C# and deserializing it but not many where you get a JSON and then return it and consume it from the view. Any help appreciated.
public JsonResult GetStuff()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(URL);
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("Stuff/?Id=" + id).Result;
*code to take response and pass it on as a JSON that I can consume from Javascript
}
Here is what I recommend.
[HttpGet("myapi/{id}");
public async Task MyApi(int id) {
// Replace these lines as needed to make your API call properly.
using HttpClient client = new() {
BaseAddress = REMOTE_SERVER_BASE
}
// Make sure to properly encode url parameters if needed
using HttpResponseMessage response = await client.GetAsync($"myapi/{id}");
this.HttpContext.Response.StatusCode = (int)response.StatusCode;
foreach (KeyValuePair<string, IEnumerable<string>> header in response.Headers) {
this.HttpContext.Response.Headers[header.Key] = new StringValues(header.Value.ToArray());
}
await response.Content.CopyToAsync(this.HttpContext.Response.Body);
}
This will copy all the common response fields such as status code, headers, and body content, over to your response.
This code isn't tested so you might have to tweak it a bit but it should be enough to get you started.

Single Instance of HttpClient vs Multiples Instances with ClientCredentias?

I'm using HttpClient to make request to a Soap Api.
I saw that is recommended to use a single instance of HttpClient instead once for each request to avoid SocketException.
In my Desktop app, I need to make multiples requests, each one with differents credentials. I'm doing it like the sample code:
WebRequestHandler requestHandler = new WebRequestHandler();
requestHandler.ClientCertificates.Add(certificate);
using(var client = new HttpClient(requestHandler))
{
try
{
HttpResponseMessage response = await client.PostAsync("https://nfe-homologacao.svrs.rs.gov.br/ws/nfeinutilizacao/nfeinutilizacao4.asmx", httpContent);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
return responseBody;
}
catch (HttpRequestException e)
{
return e.Message;
}
}
However, I'd like to avoid creates an instance of HttpClient for each request. I saw that correct way is use static instance like this:
static readonly HttpClient client = new HttpClient();
But with static instance, I can't set credentials with WebRequestHandler. Please note that credentials change in each request.
What is the better way to perform all requests, avoiding SocketException and using differents credentials?
Generally per request credentials should be set for each request created:
var request = new HttpRequestMessage();
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer");
await client.SendAsync(request);
You can also do this in your own web request handler:
class SetCredentialsWebRequestHandler : WebRequestHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer");
return base.SendAsync(request, cancellationToken);
}
}
client = new HttpClient(new SetCredentialsWebRequestHandler());
await client.PostAsync(url);
UPDATE
From the comments, I learned the question requires using certificate authentication. So in this case, multiple instances (one per certificate) would be a better option.
There will be not point to reuse the same connection for different client certificate.
Check out this named HttpClient:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1#named-clients
HttpClient could be named as per the certificate.

Performance Issue: .NET HttpClient Response is slow

Problem:
I am using .NET Class HttpClient to make Requests to the endpoint URL.
My Code:
using (HttpClient apiClient1 = new HttpClient())
{
apiClient.GetAsync(apiUrl).Result;
}
Problem Identified:
If I use using block, I'm opening multiple connections, leads to socketExceptions.
Changed My Above Code to:
public class RequestController: ApiController
{
private static HttpClient apiClient = new HttpClient();
[HttpPost]
public dynamic GetWebApiData([FromBody] ParamData params)
{
var resultContent = apiClient.GetAsync(apiUrl).Result.Content;
return Newtonsoft.Json.JsonConvert.DeserializeObject<object>(resultContent.ReadAsStringAsync().Result);
}
}
Result of the above code after making HttpClient as static is as follows:
Only one Connection is established.
For Each request, I'm looking for the 200 milliseconds reduction in
Response Time.
What I Need:
I want to make conurrent calls atleast 50 calls to the end point with High-Speed response.
Kindly help me with this scenario.
Use the async API and stop calling .Result blocking calls.
public class RequestController: ApiController {
private static HttpClient apiClient = new HttpClient();
[HttpPost]
public async Task<IHttpActionResult> GetWebApiData([FromBody] ParamData data) {
var response = await apiClient.GetAsync(apiUrl);
var resultContent = response.Content;
var model = await resultContent.ReadAsAsync<dynamic>();
return Ok(model);
}
}
The default SSL connection idle timeout of HttpClient are 2 mins. So, after that you have to re handshake with the server. This maybe the root cause.
You could follow this article(https://www.stevejgordon.co.uk/httpclient-connection-pooling-in-dotnet-core) to extend the timeout. But the testing result from my side, I could only extend to 5 mins. After that, the connection will be closed.

Categories