I'm having a problem with the .NET HttpClient class. Sometimes the snippet below throws a TaskCanceledException, and i can't debug this because is random (I had the bad luck of Apple reject my Xamarin app for that). Can someone explain to me the reason for this exception?
public static HttpResultModel RecoveryPassword(string email)
{
HttpClient httpClient = new HttpClient();
try
{
var url = String.Format(Constants.SERVER_ADDRESS + "/user/forgotPassword/{0}/", email);
var request = new HttpRequestMessage(new HttpMethod("POST"), url)
{
Content = new StringContent(email, Encoding.UTF8, "application/json"),
};
//to be more specific, this line throws the exception
var result = httpClient.SendAsync(request).Result;
string message = result.Content.ReadAsStringAsync().Result;
if (result.IsSuccessStatusCode)
{
var response = JsonConvert.DeserializeObject<HttpResultModel>(message);
response.OperationSuccess = true;
return response;
}
else
{
var response = JsonConvert.DeserializeObject<HttpResultModel>(message);
response.OperationSuccess = false;
return response;
}
}
catch (Exception ex)
{
throw ex;
}
}
}
This is due to either one of the two reasons:
A server disconnection
a timeout by the Http client. The default for HttpClient is 100 seconds.
You can set this to an infinite timespan.
httpClient.Timeout = System.Threading.Timeout.InfiniteTimeSpan;
each request can be then be set to specific timeouts if needed, as the HttpClient
timeout is on a higher level
Related
I have a C# function that carries out some validation on the front end of Blazor WASM. I have noticed something weird that I can't understand but essentially if my Http Status code is set to a 2XX.. then my wait response.WriteStringAsync(ex.Message); shows on my Blazor front End
If I have my status code set as 4XX or 5XX it doesn't show my hard coded exception messages but a general response such as net_status_code_error 404
try {
switch (name) {
case "Invalid":
throw new SpecialException("Validation exception.");
case "Bad":
throw new Exception("System exception.");
}
var url = $"{UrlRoot}/func?name={name}";
var req2 = new HttpRequestMessage(HttpMethod.Post, url){};
req2.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken.Token);
using var httpClient = new HttpClient();
var resp2 = await httpClient.SendAsync(req2);
var responseBody = await resp2.Content.ReadAsStringAsync();
if (!resp2.IsSuccessStatusCode)
{
throw new Exception($"{resp2.StatusCode}: {responseBody}");
}
}
catch (SpecialException Ex)
{
logger.LogWarning(Ex, name);
var response = req.CreateResponse(HttpStatusCode.BadRequest);
response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
await response.WriteStringAsync(Ex.Message);
return response;
}
Now at the moment this just shows the Bad Request generic error but not my ex.Message on the front end? I don't understand why it works with 2XX but not other errors
I send a request to the API and sometimes receive the response with an HTTP 429 status code (TooManyRequests).
On average, for 10 requests, 2 will return 429 response and the remaining 8 return the correct value.
It also happened to me that when it was the first request (so there is no option for TooManyRequests)
public static List<ResponseObject> GetProductsFromRestAPI(int[] ProductIdArray )
{
List<ResponseObject> products = new List<ResponseObject>();
string action;
for (int i = 0; i < ProductIdArray.Length; i++)
{
action = "products/" + ProductIdArray[i].ToString();
client = AddHeadersToClient(action, new RestClient("https://api.usedbythiscode.com/")); //required by this API.
var currentProduct = RequestsLib.GetProduct(client, action);
products.Add(currentProduct);
}
return products;
}
public static Product GetProduct(RestClient restClient, string action) //todo test this for bugs
{
var result = new Product();
var request = new RestRequest(action, Method.GET);
var response = SendRequest(restClient, request);//Here I sometimes get response with 429.
//.. Other code
return result;
}
public static async Task<IRestResponse> SendRequest(RestClient restClient, RestRequest request)
{
return await restClient.ExecuteGetAsync(request);
}
Temporarily resolved it by sending another request with do while loop and usually second request return right answer.
do
{
SendRequest(restClient, request);
}
while (StatusCode != 200);
Where could the cause of the error lie?
Is it possible that I have unclosed requests?
Is creating multiple RestSharp clients a good practice?
EDIT:
The problem was on the server side. All I had to do was report the bug to the admins who provided the API. Thank you for help.
429 is Too Many Requests. Most APIs have some kind of rate-limiting in place so that a single client can't take down their server with too many requests.
The proper response for 429 is to retry. I recommend using Polly for retry logic, but be aware that HandleTransientHttpError doesn't consider 429 a transient error.
I agree with #mason, you should use async method with Task<> and await response Here is the part of login side of my mobileApp-project in Xamarin. You may want to see how to use async with Task<> easily.
public async Task<BSUser> ValidateUser(string userName, string password)
{
string url = Xamarin.Essentials.Preferences.Get(Constants.URL_KEY, "") + "/api/Validateuser";
HttpClient _Client = new HttpClient();
var data = new Dictionary<string, string>
{
{"userName", userName},
{"password", password}
};
string jsonData = JsonConvert.SerializeObject(data);
HttpContent content = new StringContent(jsonData, Encoding.UTF8, "application/json");
try
{
HttpResponseMessage httpResponse = await _Client.PostAsync(url, content);
if (httpResponse.IsSuccessStatusCode)
{
try {
var responseData = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject(responseData).ToString();
UserInfo userInfo = JsonConvert.DeserializeObject<UserInfo>(result);
BSUser value = new BSUser();
value.UserName = userInfo.userCode;
return value;
}
catch (Java.Net.SocketException e)
{
Console.WriteLine("Hata", e);
return null;
}
}
else
{
return null;
}
}
catch (SystemException)
{
return null;
}
}
I have an Xamarin.Android APK.
When installed in a tablet locally at the company, it works perfectly. I already installed it in about 3 tablets and it is working as expected.
The problem is when I send the APK to be installed in a tablet at my customer (it is located in another state), it seems that the HttpClient is not working as expected.
To test it, I'm showing a Toast with the response of the request. It should return the Role of the user.
Locally, it returns the Role as expected. In the customer, it returns an empty string. No error is thrown locally and in the server as well.
Searching on the web, I found that this could be related with a deadlock. So, I put a .ConfigureAwait(false) on my Get method:
public static async Task<string> CheckLoginAsync(string cpf, string password)
{
try
{
_client = new HttpClient();
_client.BaseAddress = new Uri(ApiUrls.Base);
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Vault.CreateToken(cpf));
_client.Timeout = TimeSpan.FromSeconds(Constants.CheckLoginAsyncTimeout);
var url = $"{ApiUrls.UserLoginApp}/{cpf}/{password}";
var result = await _client.GetAsync(url).ConfigureAwait(false);
if (result.IsSuccessStatusCode)
{
var response = await result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<string>(response);
}
return null;
}
catch (TaskCanceledException tcex)
{
throw new TaskCanceledException("TaskCanceled", tcex.InnerException);
}
catch (Exception ex)
{
throw new LoginUnsuccessfulException("LoginFailedDB", ex.InnerException);
}
}
After that, it started working and the error started in another method. I try to do the same thing using the .ConfigureAwait(false) but the response is coming null:
private async Task<AppBaseData> GetAppBaseDataAsync(string username)
{
try
{
_client = new HttpClient();
_client.BaseAddress = new Uri(ApiUrls.Base);
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Vault.CreateToken(username));
_client.Timeout = TimeSpan.FromSeconds(Constants.LoadBaseDataAsyncTimeout);
var url = $"{ApiUrls.SupportAppBaseData}/{username}";
var result = await _client.GetAsync(url).ConfigureAwait(false);
if (result.IsSuccessStatusCode)
{
var response = await result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<AppBaseData>(response);
}
return null;
}
catch (TaskCanceledException ex)
{
throw new TaskCanceledException("TaskCanceled", ex.InnerException);
}
catch (Exception ex)
{
throw new DataNotReadedException("BaseDataDB", ex.InnerException);
}
}
I have no idea of what is causing this problem since it works with the same APK locally and in the customer is not working. The customer network have already been changed to a wifi tethering and the behavior still continues.
The above methods are inside a Task.Run().
I've got an Async method that calls an API to retrieve a JSON via HttpClient with the following code block-
//Assemble the url
string url = "https:someapi.com";
//Call API
var http = new HttpClient();
var response = new HttpResponseMessage();
try
{
response = await http.GetAsync(url);
}
catch (HttpRequestException exception)
{
//The server name or address could not be resolved
var dialog = new MessageDialog("The server name or address could not be resolved!");
dialog.Title = "API Response";
dialog.Commands.Add(new UICommand { Label = "Ok", Id = 0 });
var res = await dialog.ShowAsync();
if ((int) res.Id == 0)
{
exception.ExceptionHandled = true;// Cant' do this!
}
}
return result;
This is for an app development. I was trying to make to app more robust so that while there is no internet or data connection the app should should return that it can't call to the API service and show an error rather than crashing. But I just can't find an way to set the ExceptionHandled property to true. Is there a better way to do this?
P.S. The app crashes and debugger breaks when Ok button is clicked
You can use the IsSuccessStatusCode property from HttpClient to validate if it is a successful http response. Instead of catching a HttpRequestException, you can handle the failure in the else statement.
try
{
response = await http.GetAsync(url);
if(response.IsSuccessStatusCode)
{
//handle success
}
else
{
//handle failure
}
}
finally
{
http.Dispose();
}
Furthermore, a rule of thumb is when you use an IDisposable object, you need to wrap it inside a using statement or handle the dispose in the finally block
using(var http = HttpClient())
{
try
{
response = await http.GetAsync(url);
if(response.IsSuccessStatusCode)
{
//handle success
}
else
{
//handle failure
}
}
}
I'm trying to call PostAsync method using System.Net.Http.HttpClient from the Web API. I get the following error:
System.AggregateException "A task was canceled."
Task:
Id = 1, Status = System.Threading.Tasks.TaskStatus.Canceled, Method = "{null}", Result = "{Not yet computed}"
Code:
using (HttpClientHandler handler = new HttpClientHandler())
{
handler.Credentials = new NetworkCredential("MyUsername", "p#ssw0rd");
using (HttpClient client = new HttpClient(handler))
{
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("status", "Hello world"));
HttpContent content = new FormUrlEncodedContent(postData);
var responseTask = client.PostAsync(url, content).ContinueWith(
(postTask) =>
{
postTask.Result.EnsureSuccessStatusCode();
});
}
I assume the responseTask will force the method to run synchronously?
It's a WPF application, not ASP.NET.
I was getting this same error and tracked it down to my HttpClient was timing out. The default timeout is 100 seconds. I added the following to the create of the HttpClient.
HttpClient httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromMinutes(10);
In terms of debugging you could try writing an extension method to get the exception:
public static HttpResponseMessage PostAsyncSafe(this HttpClient client, string requestUri, string content)
{
var requestContent = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded");
return PerformActionSafe(() => (client.PostAsync(requestUri, requestContent)).Result);
}
public static HttpResponseMessage PerformActionSafe(Func<HttpResponseMessage> action)
{
try
{
return action();
}
catch (AggregateException aex)
{
Exception firstException = null;
if (aex.InnerExceptions != null && aex.InnerExceptions.Any())
{
firstException = aex.InnerExceptions.First();
if (firstException.InnerException != null)
firstException = firstException.InnerException;
}
var response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content =
new StringContent(firstException != null
? firstException.ToString()
: "Encountered an AggreggateException without any inner exceptions")
};
return response;
}
}
Not synchronously, second task will be also executed async but chained with first task, therefore only after first task executed.
Seems to be first task - PostAsync was executed with error. Try to catch TPL aggregated exceptions and find more details in inner exceptions collection from AggregateException
For example like here or subscribe to TaskScheduler.UnobservedTaskException and log there all your exceptions