Single Instance of HttpClient vs Multiples Instances with ClientCredentias? - c#

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.

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);

How to register HttpClient Handler in Blazor Client?

I want to preprocess and postprocess all outgoing requests that are going from my Blazor client to Server API. The best solution is to add custom HttpMessageHandler.
Handler code:
public class NameOfMyHandler : DelegatingHandler
{
public NameOfMyHandler()
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Debug.WriteLine("Process request");
var response = await base.SendAsync(request, cancellationToken);
Debug.WriteLine("Process response");
return response;
}
}
Code in Client->Program.cs->Main:
builder.Services.AddScoped<NameOfMyHandler>();
builder.Services.AddHttpClient("ThisIsClientCustomName", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<NameOfMyHandler>();
But when I try to do so I get an error:
An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.
but as you can see in the code I do specify the BaseAddress and I am sure it is correct.
Where is the problem ? What am I doing wrong ?
After some time I found out it requires more than adding that handler. And also it is possible to do it with AddHttpClient or pure HttpClient.
AddHttpClient example:
builder.Services.AddScoped<NameOfMyHandler>();
builder.Services.AddHttpClient("ThisIsClientCustomName",
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<NameOfMyHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("ThisIsClientCustomName"));
or
Pure HttpClient:
HttpMessageHandler httpMessageHandler = new NameOfMyHandler()
{
InnerHandler = new HttpClientHandler()
};
builder.Services.AddScoped<NameOfMyHandler>(sc => httpMessageHandler);
var httpClient = new HttpClient(httpMessageHandler)
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
};
builder.Services.AddScoped(sp => httpClient);

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);

Best Practice for Use HttpClient

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

How do I check if GetStringAsync results in a 401 (for instance)?

I have the following, wher I try to download a string from the server:
HttpClient client = new HttpClient();
var getResponsestring = await client.GetStringAsync("url");
But how do I go about and the if the server does not return the string I want, but error 401 for instance? Or any web error for that mather
You have two choices. Either break down the request into two steps.
HttpClient client = new HttpClient();
var response = await client.GetAsync("url");
if (response.IsSuccessStatusCode) {
var getResponsestring = await response.Content.ReadAsStringAsync();
}
or you insert a new MessageHandler that will return a stock response on errors.
var errorMessageHandler = new ErrorMessageHandler(new HttpClientHandler());
HttpClient client = new HttpClient(errorMessageHandler);
var getResponsestring = await client.GetStringAsync("url");
You will have to implement the ErrorMessageHandler yourself by deriving from a DelegatingHandler and overriding SendAsync.
HttpClient.GetStringAsync Method returns Task which has Exception property.
Update
When we use async/await we can just wrap the awaitable call in try/catch block to handle exceptions.

Categories