I've a .NET Core web app that, once a web method (i.e. Test()) is invoked, call another remote api.
Basically, I do it this way (here's a POST example, called within a web method Test()):
public T PostRead<T>(string baseAddress, string url, out bool succeded, object entity = null)
{
T returnValue = default(T);
succeded = false;
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
HttpResponseMessage res = null;
string json = JsonConvert.SerializeObject(entity);
var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var task = Task.Run(() => client.PostAsync($"{baseAddress}/{url}", body));
task.Wait();
res = task.Result;
succeded = res.IsSuccessStatusCode;
if (succeded)
{
returnValue = JsonConvert.DeserializeObject<T>(res.Content.ReadAsStringAsync().Result);
}
else
{
Log($"PostRead failed, error: {JsonConvert.SerializeObject(res)}");
}
}
}
catch (Exception ex)
{
Log($"PostRead error: {baseAddress}/{url} entity: {JsonConvert.SerializeObject(entity)}");
}
return returnValue;
}
Question is: if Test() in my Web App is called 10 times, in parallel, do the POST requests to the remote server are be called in parallel or in serial?
Because if they will be called in serial, the last one will take the time of the previous ones, and that's not what I want.
In fact, when I have huge list of requests, I often receive the message A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
Question is: if Test() in my Web App is called 10 times, in parallel, do the POST requests to the remote server are be called in parallel or in serial?
They will be called in parallel. Nothing in the code would make it in a blocking way.
In fact, when I have huge list of requests, I often receive the message A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
There are at least 2 things that need to be fixed here:
Properly using async/await
Properly using HttpClient
The first one is easy:
public async Task<T> PostRead<T>(string baseAddress, string url, out bool succeded, object entity = null)
{
succeded = false;
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
string json = JsonConvert.SerializeObject(entity);
var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var responseMessage = await client.PostAsync($"{baseAddress}/{url}", body);
var responseContent = await responseMessage.Content.ReadAsStringAsync();
if (responseMessage.IsSuccessStatusCode)
{
succeeded = true;
return JsonConvert.DeserializeObject<T>();
}
else
{
// log...
}
}
}
catch (Exception ex)
{
// log...
}
return default; // or default(T) depending on the c# version
}
The second one is a bit more tricky and can quite possibly be the cause of the problems you're seeing. Generally, unless you have some very specific scenario, new HttpClient() is plain wrong. It wastes resources, hides dependencies and prevents mocking. The correct way to do this is by using IHttpClientFactory, which can be very easily abstracted by using the AddHttpClient extension methods when registering the service.
Basically, this would look like this:
services.AddHttpClient<YourClassName>(x =>
{
x.BaseAddress = new Uri(baseAddress);
x.DefaultRequestHeaders.Add("Authorization", MyApiKey);
}
Then it's a matter of getting rid of all using HttpClient in the class and just:
private readonly HttpClient _client;
public MyClassName(HttpClient client) { _client = client; }
public async Task<T> PostRead<T>(string url, out bool succeded, object entity = null)
{
succeded = false;
try
{
string json = JsonConvert.SerializeObject(entity);
var responseMessage = await _client.PostAsync($"{baseAddress}/{url}", body);
//...
}
catch (Exception ex)
{
// log...
}
return default; // or default(T) depending on the c# version
}
[HttpClient call using parallel and solve JSON Parse error]
You can use task. when for parallel call . but when you use N number of calls and try to parse as JSON this may throw an error because result concatenation change the Json format so we can use Jarray. merge for combining the result .
Code
public async Task < string > (string baseAddress, string url, out bool succeded, object entity = null) {
using(HttpClient client = new HttpClient()) {
string responseContent = string.Empty;
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
string json = JsonConvert.SerializeObject(entity);
var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var tasks = new List < Task < string >> ();
var jArrayResponse = new JArray();
int count = 10; // number of call required
for (int i = 0; i <= count; i++) {
async Task < string > RemoteCall() {
var response = await client.PostAsync($"{baseAddress}/{url}", body);
return await response.Content.ReadAsStringAsync();
}
tasks.Add(RemoteCall());
}
await Task.WhenAll(tasks);
foreach(var task in tasks) {
string postResponse = await task;
if (postResponse != "[]") {
var userJArray = JArray.Parse(postResponse);
jArrayResponse.Merge(userJArray);
}
}
responseContent = jArrayResponse.ToString();
return responseContent;
}
}
Related
I have an API in .net 5 that runs locally. One of the endpoints is https://localhost:44362/User
Now, I created a console app that will consume the API and defined a method to call the get User.
public async Task<bool> CallAPI()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:44362/");
using (HttpResponseMessage response = await client.GetAsync("User"))
{
var resContent = response.Content.ReadAsStringAsync().Result;
return true;
}
}
}
Here is the code in the API
[HttpGet]
public IEnumerable<User> Get()
{
using (var context = new ohabartContext())
{
return context.Users.ToList();
}
}
But when the code using (HttpResponseMessage response = await client.GetAsync("User")) is executed nothing happens. I don't receive any error or whatsoever. I search the net and all other codes look the same in consuming or calling an API.
What seems to be the problem?
ReadAsStringAsync() Serialize the HTTP content to a string as an asynchronous operation. This operation will not block. The returned Task object will complete after all of the content has been written as a string.
HttpResponseMessage response= await client.GetAsync(u);
var resContent = await response.Content.ReadAsStringAsync().Result;
Once the operation completes, the Result property on the returned task object contains the string with the HTTP content.
public async Task<bool> CallAPI()
{
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:44362/");
HttpResponseMessage response = await client.GetAsync(u);
var resContent = awit response.Content.ReadAsStringAsync().Result;
return true;
}
}
catch (HttpRequestException e)
{
Console.WriteLine("\nException Caught!");
Console.WriteLine("Message :{0} " , e.Message);
return false;
}
}
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-6.0
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.getasync?view=net-6.0
Based on this article I decided to create single instance for my HttpClient in my project, where are sent two requests to my WebAPI from its consumer. Also to kill the connection aftrewards I decided to use solution from this article. After sending two requests using the singleton I was receiving this exception:
System.InvalidOperationException: This instance has already started
one or more requests.
So I decided to use the interface and configuration (similar) from this SO answer. Everything seems to work fine, BUT after running netstat.exe I noticed, that for my API consumer are opened two (2) different connection with various port numbers:
Note that [::1]:49153 is my WebAPI port, so I assume that [::1]:57612 and [::1]:57614 are opened for my consumer. Why? If they supposed to use the same client?
Should not it be like in the first mentioned article?
My HttpClientFactory:
public interface IHttpClientFactory
{
HttpClient CreateClient();
}
public class HttpClientFactory : IHttpClientFactory
{
static AppConfig config = new AppConfig();
static string baseAddress = config.ConnectionAPI;
public HttpClient CreateClient()
{
var client = new HttpClient();
SetupClientDefaults(client);
return client;
}
protected virtual void SetupClientDefaults(HttpClient client)
{
//client.Timeout = TimeSpan.FromSeconds(30);
client.BaseAddress = new Uri(baseAddress);
client.DefaultRequestHeaders.ConnectionClose = true;
}
}
And my two request sending methods:
public async Task<bool> SendPostRequestAsync(string serializedData)
{
CallerName = _messageService.GetCallerName();
HttpResponseMessage response = new HttpResponseMessage();
Console.Write(MessagesInfo[CallerName]);
try
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, Config.ApiPostUri);
request.Content = new StringContent(serializedData, Encoding.UTF8, "application/json");
response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode)
{
Console.WriteLine(MessagesResult[CallerName]); //success status
return true;
}
else
{
throw new Exception("Status code: " + response.StatusCode.ToString());
}
}
catch (Exception e)
{
_logger.Error(e.Message.ToString() + ", " + MessagesError[CallerName]);
CloseConnection();
return false;
}
}
and
public async Task<bool> SendGetRequestAsync()
{
CallerName = _messageService.GetCallerName();
HttpResponseMessage response = new HttpResponseMessage();
Console.Write(MessagesInfo[CallerName]);
try
{
response = await _httpClient.GetAsync(Config.ApiGetUri, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode)
{
Console.WriteLine(MessagesResult[CallerName]); //success status
return true;
}
else
{
throw new Exception("Status code: " + response.StatusCode.ToString());
}
}
catch (Exception e)
{
_logger.Error(e.Message.ToString() + ", " + MessagesError[CallerName]);
CloseConnection();
return false;
}
}
Connection is closed with:
public void CloseConnection()
{
CallerName = _messageService.GetCallerName();
var sp = ServicePointManager.FindServicePoint(new Uri(Config.ConnectionAPI));
sp.ConnectionLeaseTimeout = 1 * 1000;
Console.WriteLine();
Console.WriteLine(MessagesResult[CallerName]);
}
You need to Dispose of response, as per this discussion. You may wish to use using.
Additionally (from that link):
It's expected that the connections stay open. You don't want them to
stay open at all? By default connections are kept alive in a
connection pool for several minutes in order to avoid the expense of
creating and tearing them down for every request.
In other words, connections are pooled for future performance (for a short window of time).
Additionally, your:
var sp = ServicePointManager.FindServicePoint(new Uri(Config.ConnectionAPI));
sp.ConnectionLeaseTimeout = 1 * 1000;
should be run once at startup, not repeatedly. After doing this, you could thus remove the CloseConnection method.
I have issue with null result messages when calling a HttpClient getAsync within a foreach loop.
I need to iterate with a list of objects for values to call an API via HttpClient. After the first loop, the HttpResponseMessage's result() comes up with null message.
I tried CacheControl.NoCache = true. It's not working.
public async Task<List<Common.ProgressResponse>> RunAsync()
{
List<Response> ListOfResponses = new List<Responses>();
try
{
_client.BaseAddress = new Uri([URL]);
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue() { NoCache = true };
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add([KEY], [VALUE]);
ListOfResponses = await SomeFunction();
}
catch (Exception ex)
{
//Exceptions here...
}
return ListOfResponses;
}
private async Task<List<ListOfResponses>> SomeFunction()
{
List<Response> responses = new List<Response>();
string aPISuffix = string.Format("{0}/{1}", [APISUFFIX1], [APISUFFIX2]);
foreach (Object obj in ListOfObject)
{
_client.DefaultRequestHeaders.Add("Param1", obj.Param1);
if (!string.IsNullOrEmpty(obj.Param2))
_client.DefaultRequestHeaders.Add("Param2", obj.Param2);
Response response = new Response();
/*This is where the issue is .begin.*/
HttpResponseMessage hTTPResponse = await _client.GetAsync(aPISuffix).ConfigureAwait(false);
string result = hTTPResponse.Content.ReadAsStringAsync().Result;
/*This is where the issue is .end.*/
if (hTTPResponse.IsSuccessStatusCode)
{
response = [Code here...]
//Codes here...
}
responses.Add(response);
}
return responses;
}
On: 'string result = hTTPResponse.Content.ReadAsStringAsync().Result;'
I would expect the result message would have values as it loops through ListOfObjects.
Update: Per comment
First off, I generally try to avoid using .Result with tasks. I also don't think adding the cache headers is the issue since you are making a server to server call.
One thing I tend to always do is evaluate the response before I try to read it. I also prefer to use the HttpRequestMessage so I can manage the disposal of it.
Here is a quick example demonstrating how call out:
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Testing
{
class Program
{
static void Main(string[] args)
{
TestAsync().Wait();
}
static async Task TestAsync()
{
var urls = new string[]
{
"https://stackoverflow.com/questions/57084989/null-message-on-httpresponse-content-readasstringasync-result-after-1st-foreac",
"https://stackoverflow.com/users/2025711/rogala"
};
using (var httpClient = new HttpClient())
{
foreach (var url in urls)
{
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
{
Console.WriteLine($"Request: {url}".PadLeft(5,'*').PadRight(5, '*'));
var response = await httpClient.SendAsync(request)
.ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync()
.ConfigureAwait(false);
Console.WriteLine($"{body.Length}{Environment.NewLine}");
}
else
{
Console.WriteLine($"*Bad request: {response.StatusCode}");
}
}
}
}
Console.ReadKey();
}
}
}
One additional thing to keep in mind is socket exhaustion. If this code is running on a server, then you will run into socket exhaustion once your service gets some load. I would highly recommend using an HttpClientFactory to handle the HttpClients.
With respect to why the content string is empty, it could be because the server didn't return content which could be a server issue. The server may have also throttled you, which resulted in no content. I would recommend looking into Polly to handle certain response codes.
Solved it... I moved the .DefaultRequestHeaders values inside the foreach() loop.
I suspected that they were pilling up at every loop iteration. I had to clear them and set them up before the GetAsync()
Updated code here:
public async Task<List<Common.ProgressResponse>> RunAsync()
{
List<Response> ListOfResponses = new List<Responses>();
try
{
_client.BaseAddress = new Uri([URL]);
ListOfResponses = await SomeFunction();
}
catch (Exception ex)
{
//Exceptions here...
}
return ListOfResponses;
}
private async Task<List<ListOfResponses>> SomeFunction()
{
List<Response> responses = new List<Response>();
string aPISuffix = string.Format("{0}/{1}", [APISUFFIX1], [APISUFFIX2]);
foreach (Object obj in ListOfObject)
{
/*Moved Code .begin.*/
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue() { NoCache = true };
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add([KEY], [VALUE]);
/*Moved Code .end.*/
_client.DefaultRequestHeaders.Add("Param1", obj.Param1);
if (!string.IsNullOrEmpty(obj.Param2))
_client.DefaultRequestHeaders.Add("Param2", obj.Param2);
Response response = new Response();
HttpResponseMessage hTTPResponse = await _client.GetAsync(aPISuffix).ConfigureAwait(false);
if (hTTPResponse.IsSuccessStatusCode)
{
string result = hTTPResponse.Content.ReadAsStringAsync().Result;
response = [Code here...]
//Codes here...
}
responses.Add(response);
}
return responses;
}
I have written an API endpoint and created a simple .net core console app to send off multiple requests to the API endpoint simultaneously to test how the API endpoint works
The code looks as below
static async Task Main(string[] args)
{
// ARRANGE
int request_number = 200;
Task<HttpResponseMessage>[] tasks = new Task<HttpResponseMessage>[request_number];
Action[] actions = new Action[request_number];
for (int i = 0; i < request_number; i++)
{
int temp = i;
actions[temp] = () =>
{
tasks[temp] = CallMyAPI();
};
}
// ACT
Parallel.Invoke(actions);
await Task.WhenAll(tasks);
// ASSERT
string sample1 = await tasks[0].Content.ReadAsStringAsync();
for (int i = 1; i < request_number; i++)
{
string toBeTested = await tasks[i].Content.ReadAsStringAsync();
if (toBeTested != sample1)
{
Console.WriteLine("Wrong! i = " + i);
}
}
Console.WriteLine("finished");
Console.WriteLine("Press any key to complete...");
Console.Read();
}
static async Task<HttpResponseMessage> CallMyAPI()
{
var request = new HttpRequestMessage();
request.Method = HttpMethod.Post;
string contentString = "some json string as http body";
request.Content = new StringContent(contentString, System.Text.Encoding.UTF8, "application/json");
request.RequestUri = new Uri("http://myAPIendpoint.com");
HttpResponseMessage response;
using (HttpClient httpClient = new HttpClient())
{
response = await httpClient.SendAsync(request);
}
return response;
}
So basically what I have been trying to do by the code is to send off multiple requests once, and wait and record all the responses. Then I compare them to verify that they all return the same response.
Initially, when I set the variable request_number as small numbers, like 50, 100, the test app runs well. However, as the request_numbergoes up and reaches around 200, it starts to throw an exception that looks like:
Inner Exception 1:
IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request.
Inner Exception 2:
SocketException: The I/O operation has been aborted because of either a thread exit or an application request
What is this kind of exception supposed to mean?
Your problem is this:
using (HttpClient httpClient = new HttpClient())
{..
}
Each of these uses a new socket.
Use a single httpclient, e.g. a static one
I am trying to make a call to a service, the service is supposed to return all the records but i am getting a TaskCancelledException with a message A Task was cancelled and then the results are null. I know there is lots of data (tried with small data and works fine). How can I make it so it continues working (keep grabbing records) without throwing the "A Task was cancelled". Here is my method
public async Task<ActionResult<string>> ObtainData(string url, CancellationToken ct = default(CancellationToken))
{
Result<string> result = new Result<string>();
try
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
HttpResponseMessage response = await httpClient.SendAsync(request); //HttpClient
string jsonresult = await HandleReceipt(response, type);
result.Result = jsonresult;
}
catch (Exception ex)
{
result.Message = "Problem obtaining data.";
}
return result;
}
I see there is a cancellation token being passed, is there a way to handle the cancellation?. Also, this method is being called from here:
public async void GetJsonData()
{
string json = await ObtainData();
}
I would like it to complete getting the request without cancelling.
It sounds from your question and comments that your request is timing out. Try setting the HttpClient.Timeout property
public async Task<ActionResult<string>> ObtainData(string url, CancellationToken ct = default(CancellationToken))
{
Result<string> result = new Result<string>();
try
{
var request = (HttpWebRequest) WebRequest.Create(url);
request.Method = "GET";
HttpResponseMessage response = await httpClient.SendAsync(request); //HttpClient
httpClient.TimeOut = TimeSpan.MaxValue // change this as you like
string jsonresult = await HandleReceipt(response, type);
result.Result = jsonresult;
}
catch (Exception ex)
{
result.Message = "Problem obtaining data.";
}
return result;
}
Edit
Regarding you comment about the exception, this is from MSDN:
The Timeout property must be set before the GetRequestStream or GetResponse method is called.
Create a new instance of HttpClient before making the request, or set it inside the code where you initialize the client (constructor i assume).
As Yuval Itzchakov mentioned you should set the Timeout property to a larger value (but it is a TimeSpan not an integer) and you should use the cancellation token in the HTTP request call if you are using with the async pattern.
And there is no need to use a global HttpClient (as Yuval pointed out) esspecially in this heavy async method calling nightmare (additionally i don't see the reason why should call anything inside the ObtainData async).
public async Task<ActionResult<string>> ObtainData(string url, CancellationToken ct = default(CancellationToken))
{
try
{
using (var httpClient = new HttpClient
{
Timeout = TimeSpan.MaxValue
})
{
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
{
using (var response = await httpClient.SendAsync(request, ct))
{
return new ActionResult<string>()
{
Result = await HandleReceipt(response, type)
};
}
}
}
}
catch (Exception ex)
{
return new ActionResult<string>
{
Message = "Problem obtaining data. " + ex
};
}
}