Deadlock when calling HttpClient.PostAsync - c#

I'm currently working on a project which consists of 2 application, one that's basically a rest server and a client. On the server part all seems to work fine but I have a deadlock when posting async in the client application.
The post handler in the server application is as for now (using Nancy):
Post("/", args =>
{
bool updated = false;
string json = Request.Body.AsString();
PostData data = JsonConvert.DeserializeObject<PostData>(json);
int clientCode = data.ClientCode;
bool started = data.Started;
// TODO:
// Update database
return updated;
}
);
The deadlocking part of the client application is (I'm also adding the GET handling just for completeness, but it's working fine):
public async Task<string> Get()
{
string actualRequest = $"{uri}/{request}";
response = await client.GetAsync(actualRequest);
response.EnsureSuccessStatusCode();
result = await response.Content.ReadAsStringAsync();
return result;
}
public async Task<string> Post(object data)
{
string json = JsonConvert.SerializeObject(data);
StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
response = await client.PostAsync(uri, content);
result = await response.Content.ReadAsStringAsync();
return result;
}
The small chunk of code used for testing is:
private static string response = "";
private static async Task TestPostConnection(RestClient client)
{
PostData data = new PostData(1, true);
response = await client.Post(data);
}
private static async Task TestGetConnection(RestClient client)
{
client.Request = "id=1";
response = await client.Get();
}
private static async Task InitializeClient()
{
string ipAddress = "localhost";
RestClient client = new RestClient("RestClient", new Uri($"http://{ipAddress}:{8080}"));
await TestGetConnection(client); // OK
await TestPostConnection(client);
}
Probably it's something stupid that I can't see because it's like 4 hours that I'm working on it :P
Thank in advantage!
Edit:
I'm using .net framework (4.7.2) and I'm not calling Result anywhere (just searched in Visual Studio for double check)
This is the Main method:
[STAThread]
static async Task Main()
{
await InitializeClient(); // Call the async methods above
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new ClientForm(response))
}

I just solved the it.
The problem was the return type of the post handler in the server application
Post("/", args =>
{
bool updated = false;
string json = Request.Body.AsString();
PostData data = JsonConvert.DeserializeObject<PostData>(json);
int clientCode = data.ClientCode;
bool started = data.Started;
// TODO:
// Update database
return updated.ToString();
}
);
The RestClient.Post() that I've implemented was expecting a string as a result (in fact the type of the method si (async) Task<string> but I was returning a bool).
Adding ToString() solved it.
Thanks all!

Related

How to have an API call working? Currently my call to client is not working

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

Does these HttpClient call be called in parallel?

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

Querying WebAPI - webapi Cannot deserialize the current JSON array

I have built a Web API using MVC and it works as expected.
I am now trying to query the API from a console application and I am hitting an issue. I understand why I am getting the issue but I dont understand how to fix it.
My Code from the console application:
static HttpClient client = new HttpClient();
static void Main(string[] args)
{
RunAsync().GetAwaiter().GetResult();
}
static async Task RunAsync()
{
client.BaseAddress = new Uri(URL);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
List<TagDetail> tagDetail = new List<TagDetail>();
tagDetail = await GetTagDetailAsync("api/tagdetail/?tagname=myTag&startdate=010120190000&enddate=020120190000");
Console.WriteLine(tagDetail.value);
}
static async Task<TagDetail> GetTagDetailAsync(string path)
{
List<TagDetail> tagdetail = new List<TagDetail>();
HttpResponseMessage response = await client.GetAsync(path);
var test = response.StatusCode;
var test2 = response.Headers;
if (response.IsSuccessStatusCode)
{
tagdetail = await response.Content.ReadAsAsync<List<TagDetail>>(
new List<MediaTypeFormatter>
{
new XmlMediaTypeFormatter(),
new JsonMediaTypeFormatter()
});
}
return tagdetail;
}
The error I am getting is on the lines:
tagDetail = await GetTagDetailAsync("api/tagdetail/?tagname=99TOTMW&startdate=010120190000&enddate=020120190000");
And
return tagdetail;
The Web API returns the data in JSON format which looks like:
{
"tagname":"myTag",
"value":"99.99",
"description":"myDescription",
"units":"£",
"quality":"Good",
"timestamp":"2019-08-01T17:32:30"
},
{
"tagname":"myTag",
"value":"22.22",
"description":"myDescription",
"units":"£",
"quality":"Good",
"timestamp":"2019-08-01T17:33:30"
}
The TagDetail class is just declaration of each of the fields you see above.
The webapi provide the means of selecting a date range so I would get numerous TagDetails back as a List but it can also return just one (I can get this working by changing my code a bit). I need it to work for either one result or multiple.
As the comment has explained that you need to return List<TagDetail> for your GetTagDetailAsync.Then you could use foreach to loop the result.This will work for one or multiple TagDetail
static async Task RunAsync()
{
//other logic
List<TagDetail> tagDetail = new List<TagDetail>();
tagDetail = await GetTagDetailAsync("api/tagdetail/?tagname=myTag&startdate=010120190000&enddate=020120190000");
foreach(var item in tagDetail)
{
Console.WriteLine(item.value);
}
}
static async Task<List<TagDetail>> GetTagDetailAsync(string path)
{
List<TagDetail> tagdetail = new List<TagDetail>();
HttpResponseMessage response = await client.GetAsync(path);
var test = response.StatusCode;
var test2 = response.Headers;
if (response.IsSuccessStatusCode)
{
tagdetail = await response.Content.ReadAsAsync<List<TagDetail>>(
new List<MediaTypeFormatter>
{
new XmlMediaTypeFormatter(),
new JsonMediaTypeFormatter()
});
}
return tagdetail;
}

call REST API in xamarin

I design a login form in xamarin Form PCL and I want to call a my webservice that will return JSON. For this, I created two functions for the same but both are n is not returning values.
Can you please tell me what I am doing wrong?
async void OnLoginButtonClick(object sender, EventArgs e)
{
if (usernameEntry.Text != "" && passwordEntry.Text != "")
{
var response = GetLoginDetails(usernameEntry.Text,passwordEntry.Text);
var getData = await getDataFromService(usernameEntry.Text, passwordEntry.Text);
}
}
public static async Task<HttpResponseMessage> GetLoginDetails(string username, string password)
{
try
{
var httpClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://mywebserverIP/api/Users?Username=" + username + "&Password=" + password);
var response = await httpClient.SendAsync(request);
return response;
}
catch (Exception ex)
{
throw;
}
}
public static async Task<dynamic> getDataFromService(string username, string password)
{
using (var client = new HttpClient())
{
var responseText = await client.GetStringAsync("http://mywebserverIP/api/Users?Username=" + username + "&Password=" + password);
dynamic data = JsonConvert.DeserializeObject(responseText);
return data;
}
}
Thanks for your comment in Advance.
As you not awaiting the first method , request thread will not wait till it returns the value so , 1st change you have to make is to
var response = await GetLoginDetails()
For the second method
var getData = await getDataFromService()
I do not see any issue. I am not sure how you know that this method is not returning any values. Better to log the response of the both the method call and check.
Use await.
First in the
var response = await GetLoginDetails(...
then maybe in the deserializeobject method too (this one i'm not sure)
dynamic data = await Task.Run(() => JsonConvert.DeserializeObject(responseText));

await httpClient.SendAsync(httpContent) is non responsive

await httpClient.SendAsync(httpContent) is not responding though I found no error in code/url its still getting hang. Please suggest/help.
My code as follows:
public async Task<string> Get_API_Result_String(string url, List<KeyValuePair<string, string>> parameters)
{
string res = "";
try
{
IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
//Prepare url
Uri mainurl = new Uri(settings[FSAPARAM.UserSettingsParam.SERVERNAME].ToString());
Uri requesturl = new Uri(mainurl, url);
var httpClient = new HttpClient();
var httpContent = new HttpRequestMessage(HttpMethod.Post, requesturl);
// httpContent.Headers.ExpectContinue = false;
httpContent.Content = new FormUrlEncodedContent(parameters);
HttpResponseMessage response = await httpClient.SendAsync(httpContent);
var result = await response.Content.ReadAsStringAsync();
res = result.ToString();
response.Dispose();
httpClient.Dispose();
httpContent.Dispose();
}
catch (Exception ex)
{
Logger l = new Logger();
l.LogInfo("Get_API_Result_String: "+ url + ex.Message.ToString());
ex = null;
l = null;
}
return res;
}
Calling it in another class as follows:
NetUtil u = new NetUtil();
string result = await u.Get_API_Result_String(Register_API, values);
u = null;
I predict that further up your call stack, you are calling Wait or Result on a returned Task. This will cause a deadlock that I explain fully on my blog.
To summarize, await will capture a context and use that to resume the async method; on a UI application this is a UI thread. However, if the UI thread is blocked (in a call to Wait or Result), then that thread is not available to resume the async method.
this worked for me:
httpClient.SendAsync(httpContent).ConfigureAwait(false);
I just removed await and just used as below and it worked:
var result = httpClient.SendAsync(httpContent).Result;
But that is not a good practice.
As
Nikola mentioned, we shouldn't mix sync and async calls.
I changed the calling method to async and problem got resolved.
This is OK on mine
var response = httpClient.SendAsync(request);
var responseResult = response.Result;
if (responseResult.IsSuccessStatusCode)
{
var result = responseResult.Content.ReadAsStringAsync().Result;
return result;
}
Just got this bug when returning compressed gzip data. It's a very rare case as 99% times with different input data everything is fine. Had to switch to HttpWebRequest.

Categories