HttpClient How to catch every response in one place - c#

I'm working on Xamarin.Forms App connected with Web Api 2 Api and all requests and responses work with HttClient. This is my code:
class for all my requests and definiot of HttpClient
public class DataStore : IDataStore<object>
{
HttpClient client;
public DataStore()
{
client = new HttpClient()
{
BaseAddress = new Uri($"{App.Uri}")
};
}
Example of one of my requests :
public async Task<User> GetProfileSetup()
{
try
{
if (CrossConnectivity.Current.IsConnected)
{
string token = DependencyService.Get<ISharedFunctions>().GetAccessToken();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetAsync(#"api/User/GetProfilSetup");
if (response.IsSuccessStatusCode)
{
string jsonMessage;
using (Stream responseStream = await response.Content.ReadAsStreamAsync())
{
jsonMessage = new StreamReader(responseStream).ReadToEnd();
}
User user = JsonConvert.DeserializeObject<User>(jsonMessage);
return user;
}
else
{
var m = response.Content.ToString();
return null;
}
}
else
{
return null;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
string error = ex.Message;
return null;
}
}
My idea is to check every response(Response Status Code) in one place. I need this for throw Alert Errors , for refresh token etc. Is there a possible way to this ? I want to have control on every request/response.

if anyone have problem with this , just need to implement custom handler , who will inherit form DelegatingHandler. My code example:
public class StatusCodeHandler : DelegatingHandler
{
public StatusCodeHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
public GetStatusCode GetStatusCode = new GetStatusCode();
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = null;
response = await base.SendAsync(request, cancellationToken);
if (response.IsSuccessStatusCode)
{
return response;
}
else
{
var status_code = (int)response.StatusCode;
GetStatusCode.GetResponseCode(status_code);
}
return response;
}
}

This is not related to xamarin, its a question of abstraction in OOP. You can and should abstract HttpClient and its methods to remove all the boilerplate.
Example - GetAsync<T>(url) will check for connectivity, forms request adds necessary headers, waits for response, checks response status, reads response and finally returns the deserialised response. That way, if you want to add caching layer it's easier. Basic OOP.
Abstracting your code:
public async Task<T> GetAsync(string url)
{
try
{
if (!CrossConnectivity.Current.IsConnected)
{
// throw custom exception?
new NoNetworkException();
}
var token = DependencyService.Get<ISharedFunctions>().GetAccessToken();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
// read response and throw for logging?
new InvaidResponseException();// custom exceptions makes it easier for catching
}
using (Stream responseStream = await response.Content.ReadAsStreamAsync())
{
// there should be an async overload to read too
var jsonMessage = new StreamReader(responseStream).ReadToEnd();
return JsonConvert.DeserializeObject<T>(jsonMessage);
}
}
catch(NoNetworkException ex)
{
// handle
}
catch(InvaidResponseException ex)
{
// handle
}
}

Related

Xamarin - Logout when token expires

I have an Api hosted on Azure which I consume on my Xamarin Forms project.
I show the login page at the beginning and I check if the JWT token has expired but I also want to check that on each http method in case it expires while the user is using the app.
So I need to either show the user the login page and tell them to login again I have been searching how to do that I can't get it right.
Here is my AzureApiService class.
public class AzureApiService
{
HttpClient httpClient;
public AzureApiService()
{
#if DEBUG
var httpHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (o, cert, chain, errors) => true
};
#else
var httpHandler = new HttpClientHandler();
#endif
httpClient = new HttpClient(httpHandler);
httpClient.Timeout = TimeSpan.FromSeconds(15);
httpClient.MaxResponseContentBufferSize = 256000;
}
public async Task<string> LoginAsync(string url, AuthUser data)
{
var user = await HttpLoginPostAsync(url, data);
if (user != null)
{
//Save data on constants
CurrentPropertiesService.SaveUser(user);
return user.Token;
}
else
{
return string.Empty;
}
}
// Generic Get Method
public async Task<T> HttpGetAsync<T>(string url, string token)
{
T result = default(T);
try
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = httpClient.GetAsync(url).Result;
HttpContent content = response.Content;
if (response.IsSuccessStatusCode)
{
var jsonResponse = await content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<T>(jsonResponse);
}
else
{
if (IsExpired(token))
{
await Logout();
}
throw new Exception(((int)response.StatusCode).ToString() + " - " + response.ReasonPhrase);
}
}
catch (Exception ex)
{
OnError(ex.ToString());
}
return result;
}
// Generic Post Method
public async Task<T> HttpPostAsync<T>(string url, string token, T data)
{
T result = default(T); // résultat de type générique
try
{
string json = JsonConvert.SerializeObject(data);
StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.PostAsync(new Uri(url), content);
var jsonResponse = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
var jsons = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<T>(jsonResponse);
}
else
{
if (IsExpired(token))
{
await Logout();
}
throw new Exception(((int)response.StatusCode).ToString() + " - " + response.ReasonPhrase);
}
return result;
}
catch (Exception ex)
{
OnError(ex.ToString());
return result;
}
}
// Generic Put Method
public async Task<T> HttpPutAsync<T>(string url, string token, T data)
{
T result = default(T); // résultat de type générique
try
{
string json = JsonConvert.SerializeObject(data);
StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.PutAsync(new Uri(url), content);
if (response.IsSuccessStatusCode)
{
var jsonResponse = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<T>(jsonResponse);
}
else
{
if (IsExpired(token))
{
await Logout();
}
throw new Exception(((int)response.StatusCode).ToString() + " - " + response.ReasonPhrase);
}
return result;
}
catch (Exception ex)
{
OnError(ex.ToString());
return result;
}
}
// Generic Delete Method
public async Task<bool> HttpDeleteAsync(string url, string token)
{
try
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.DeleteAsync(url);
if (response.IsSuccessStatusCode)
{
return true;
}
else
{
if (IsExpired(token))
{
await Logout();
}
return false;
throw new Exception(((int)response.StatusCode).ToString() + " - " + response.ReasonPhrase);
}
}
catch (Exception ex)
{
OnError(ex.ToString());
return false;
}
}
// Login Post Method
public async Task<T> HttpLoginPostAsync<T>(string url, T data)
{
T result = default(T); // résultat de type générique
try
{
string json = JsonConvert.SerializeObject(data);
StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(new Uri(url), content);
if (response.IsSuccessStatusCode)
{
var jsonResponse = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<T>(jsonResponse);
}
else
{
throw new Exception(((int)response.StatusCode).ToString() + " - " + response.ReasonPhrase);
}
return result;
}
catch (Exception ex)
{
OnError(ex.ToString());
return result;
}
}
public bool IsExpired(string token)
{
if (token == null || "".Equals(token))
{
return true;
}
/***
* Make string valid for FromBase64String
* FromBase64String cannot accept '.' characters and only accepts stringth whose length is a multitude of 4
* If the string doesn't have the correct length trailing padding '=' characters should be added.
*/
int indexOfFirstPoint = token.IndexOf('.') + 1;
String toDecode = token.Substring(indexOfFirstPoint, token.LastIndexOf('.') - indexOfFirstPoint);
while (toDecode.Length % 4 != 0)
{
toDecode += '=';
}
//Decode the string
string decodedString = Encoding.ASCII.GetString(Convert.FromBase64String(toDecode));
//Get the "exp" part of the string
Regex regex = new Regex("(\"exp\":)([0-9]{1,})");
Match match = regex.Match(decodedString);
long timestamp = Convert.ToInt64(match.Groups[2].Value);
DateTime date = new DateTime(1970, 1, 1).AddSeconds(timestamp);
DateTime compareTo = DateTime.UtcNow;
int result = DateTime.Compare(date, compareTo);
return result < 0;
}
private async Task Logout()
{
CurrentPropertiesService.Logout();
CurrentPropertiesService.RemoveCart();
await Shell.Current.GoToAsync($"//main");
}
private void OnError(string error)
{
Console.WriteLine("[WEBSERVICE ERROR] " + error);
}
}
So you can see that in each http method I'm trying yo check if the token has expired already and then logout but it just gives an error.
On my Logout method I just want to delete all the properties and then navigate to the login page but it isn't working.
Please help I would like to know how to do this. Thanks.
EDIT
Trying to implement DelegatingHandler stops at SendAsync
Here is my HttpDelegatingHandler class
public class HttpDelegatingHandler : DelegatingHandler
{
public HttpDelegatingHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("Bearer", CurrentPropertiesService.GetToken());
// before request
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
// after request
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
await Logout();
}
return response;
}
private async Task Logout()
{
CurrentPropertiesService.Logout();
CurrentPropertiesService.RemoveCart();
await Shell.Current.GoToAsync($"//main");
}
}
Here my AzureApiService class
public class AzureApiService
{
HttpClient httpClient;
public AzureApiService()
{
var clientHandler = new HttpClientHandler();
#if DEBUG
clientHandler.ServerCertificateCustomValidationCallback =
(sender, cert, chain, sslPolicyErrors) =>
{
return true;
};
#endif
httpClient = new HttpClient(new HttpDelegatingHandler(clientHandler));
httpClient.Timeout = TimeSpan.FromSeconds(15);
httpClient.MaxResponseContentBufferSize = 256000;
}
public async Task<string> LoginAsync(string url, AuthUser data)
{
var user = await HttpLoginPostAsync(url, data);
if (user != null)
{
//Save data on constants
CurrentPropertiesService.SaveUser(user);
return user.Token;
}
else
{
return string.Empty;
}
}
// Generic Get Method
public async Task<T> HttpGetAsync<T>(string url, string token)
{
T result = default(T);
try
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.GetAsync(url);
HttpContent content = response.Content;
var jsonResponse = await content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<T>(jsonResponse);
throw new Exception(((int)response.StatusCode).ToString() + " - " + response.ReasonPhrase);
}
catch (Exception ex)
{
OnError(ex.ToString());
}
return result;
}
It works for PostAsync
// Login Post Method
public async Task<T> HttpLoginPostAsync<T>(string url, T data)
{
T result = default(T); // résultat de type générique
try
{
string json = JsonConvert.SerializeObject(data);
StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(new Uri(url), content);
var jsonResponse = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<T>(jsonResponse);
return result;
}
catch (Exception ex)
{
OnError(ex.ToString());
return result;
}
}
But as I said it stops when trying to get data
You can handle 401 Unauthorized response in a custom Delegating handler. This way you can handle anything before and after request execution in a single place.
public class HttpDelegatingHandler : DelegatingHandler
{
public HttpDelegatingHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("Authorization", string.Format("Basic {0}", MyUserRepository.AuthToken));
// before request
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
// after request
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
await Shell.Current.GoToAsync($"//main");
}
return response;
}
}
public class AzureApiService
{
HttpClient httpClient;
public AzureApiService()
{
var clientHandler = new HttpClientHandler();
#if DEBUG
clientHandler.ServerCertificateCustomValidationCallback =
(sender, cert, chain, sslPolicyErrors) =>
{
return true;
};
#endif
httpClient = new HttpClient(new HttpDelegatingHandler(clientHandler));
httpClient.Timeout = TimeSpan.FromSeconds(15);
httpClient.MaxResponseContentBufferSize = 256000;
}
....
// Generic Get Method
public async Task<T> HttpGetAsync<T>(string url, string token)
{
T result = default(T);
try
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await httpClient.GetAsync(url);
var jsonResponse = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<T>(jsonResponse);
}
catch (Exception ex)
{
OnError(ex.ToString());
}
return result;
}

How can I consume a FileContentResult returned from an .Net Core api inside a .Net WebApi?

I am trying something similar posted [here][1]
[1]: .Net Core Receive FileContentResult from one API to Another This is what I am trying and I think I am very close,just that not sure how to receive the in the receiver Api a response which is being sent by the Producer Api as FileContentResult, any help will be appreciated.
My effort is listed below:
//Source API-working well
[HttpPost("download")]
public async Task<FileContentResult> ExportApplicationUsers2(ExportApplicationUsersQuery command)
{
try
{
var filePath = await Mediator.Send(command);
return File(System.IO.File.ReadAllBytes(filePath), "application/octet-stream");
}
catch (Exception ex)
{
throw ex.InnerException;
}
}
//Controller in Consumer Api
public async Task<Result<FileContentResult>> ExportApplicationUsers(ExportUsersRequest model)
{
try
{
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.PostFileAsync("/users/download", JsonConvert.SerializeObject(model));
return response;
//return File(System.IO.File.ReadAllBytes(response), "application/octet-stream");
}
catch (Exception ex)
{
_logger.Error(ex.Message, ex);
return new Result<FileContentResult>(new List<string> { ex.Message });
}
}
The Actual PostFileAsync() which I need to fix first:
public async Task<FileContentResult> PostFileAsync(string uri, object data)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
if (data != null)
{
request.Content = new StringContent(data.ToString(), Encoding.UTF8, "application/json");
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
var response = default(FileContentResult);
var actionTask = _httpClient.SendAsync(request)
.ContinueWith(responseTask =>
{
FileStreamResult resposneMessage = responseTask.Result;
response = (FileContentResult)resposneMessage.ReadAsStreamAsync().Result;//This is where I need help as its not able to convert into FileContentResult!
});
actionTask.Wait();
return response;
}

Cannot send the same request message multiple times using Func<HttpRequestMessage>

Our API has retry logic for calling another endpoint. But it kept giving me an error of
The request message was already sent. Cannot send the same request
message multiple times
Here's my code
public async Task<object> GetResponse()
{
var httpRequestMessage = ConstructHttpRequestForBatchUpdate(batchRequest, client, requestUri);
HttpResponseMessage httpResponseMessage = await _retryHttpRequest.ExecuteAsync(() => httpRequestMessage, client, maxRetryValue);
}
private HttpRequestMessage ConstructHttpRequestForBatchUpdate(JArray batchRequest, HttpClient client, Uri requestUri)
{
var batchReqStr = JsonConvert.SerializeObject(batchRequest);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var httpRequestMessage = new HttpRequestMessage()
{
Content = new StringContent(batchReqStr, Encoding.UTF8, "application/json"),
Method = HttpMethod.Put,
RequestUri = requestUri
};
return httpRequestMessage;
}
public class RetryHttpRequest : IRetryHttpRequest
{
public async Task<HttpResponseMessage> ExecuteAsync(Func<HttpRequestMessage> requestMessage, HttpClient client, int maxTryValue)
{
var remainingTries = maxTryValue;
var exceptions = new List<Exception>();
do
{
--remainingTries;
try
{
return await ExecuteSingleAsync(requestMessage(), client);
}
catch (Exception e)
{
exceptions.Add(e);
}
}
while (remainingTries > 0);
throw new AggregateException(exceptions);
}
public async Task<HttpResponseMessage> ExecuteSingleAsync(HttpRequestMessage requestMessage, HttpClient client)
{
try
{
HttpResponseMessage httpResponseMessage = await client.SendAsync(requestMessage);
if (httpResponseMessage.IsSuccessStatusCode)
{
return httpResponseMessage;
}
else
{
var exception = new InvalidOperationException();
throw new Exception();
}
}
catch (HttpRequestException httpException)
{
throw httpException;
}
}
}
To my understanding, Func<HttpRequestMessage> allows it to create a new instance of HttpRequestMessage. For example, for this line of code
return await ExecuteSingleAsync(requestMessage(), client);
requestMessage() is creating a new instance for every loop. But if my understanding is correct, i am not sure why it is still giving me this error of sending the same request.
requestMessage() is creating a new instance for every loop.
This is not the case - the Func you handed in is () => httpRequestMessage, which always returns the same instance.
Try this instead:
_retryHttpRequest.ExecuteAsync(() => ConstructHttpRequestForBatchUpdate(batchRequest, client, requestUri), client, maxRetryValue);

How to set the Timeout for request and get the response code for HttpRequest in MvvmCross xamarin

Code I have constructed so for:
public class RestService : IRestService
{
public async Task<StellaData> GetStellConfigData()
{
try
{
//Declare a Http client
HttpClient client = new HttpClient();
//Add a Base URl
//client.BaseAddress = new Uri(Constants.MUrl);
//Add the response type
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Add the API
var response =await client.GetStringAsync(new Uri(Constants.mUrl));
var myItems = Newtonsoft.Json.JsonConvert.DeserializeObject<StellaData>(response);
return myItems;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
return null;
}
}
What I am trying to do:
Set a Timeout for the request
Catch Related Exception for the timeout for the request
Get the response code for the request
This question really has nothing to do with MvvmCross, Xamarin or Android, since you're using the same HTTP client you would in any .NET application. Nevertheless, HttpClient has a Timeout property which you can set to ensure your requests time out after a certain interval. I've changed GetStringAsync to GetAsync, since GetAsync will throw a TaskCanceledException if the request times out, which you can catch and handle. GetStringAsync would handle the timeout internally, and you wouldn't be able to catch it. I've rewritten your method to achieve that (this example has a 30-second timeout), as well as assign the status code to a variable for you to use:
public async Task<StellaData> GetStellConfigData()
{
try
{
using (var client = new HttpClient
{
Timeout = TimeSpan.FromMilliseconds(30000)
})
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync(new Uri(Constants.mUrl));
HttpStatusCode statusCode = response.StatusCode;
var myItems = Newtonsoft.Json.JsonConvert.DeserializeObject<StellaData>(await response.Content.ReadAsStringAsync());
return myItems;
}
}
catch (TaskCanceledException tcex)
{
// The request timed out
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
return null;
}
This class can be refactored to reuse the client instead of creating a new instance for each request. Set the time-out on the client when initialized.
public class RestService : IRestService {
private static HttpClient client = new HttpClient();
static RestService() {
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Timeout = TimeSpan.FromMilliseconds(Constants.DefaultTimeOut);
}
public async Task<StellaData> GetStellConfigData() {
try {
//Add the API
using(var response = await client.GetAsync(new Uri(Constants.mUrl))) {
if (response.IsSuccessStatusCode) {
return await response.Content.ReadAsAsync<StellaData>();
}
}
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(ex.Message);
}
return null;
}
}

C# return from async task not working

I'm working on an async http call using HttpClient. The call is made inside an async task. The call is successful and I get a response from the Http call. But when I try to return the response from the task nothing happens, even though I have a breakpoint waiting after the return.
public void ExecuteTask(Foundation.Security.SecurityToken token, Order order)
{
ExecuteTaskAsync(token, order).Wait();
}
public async Task ExecuteTaskAsync(Foundation.Security.SecurityToken token, Order order)
{
if (order != null)
{
log.Info("Starting export of order " + order.ID.ToString());
bool success = await ExportOrder(order, token);
if (!success)
{
log.Error("Failed to export order with ID " + order.ID.ToString());
}
}
}
private async Task<bool> ExportOrder(Order order, Foundation.Security.SecurityToken token)
{
try
{
ResponseObject response = await webService.SendOrder(new SenderInformation(token), new ReceiverInformation(order, token));
if (response.Success && response.Status.Equals("201", StringComparison.OrdinalIgnoreCase))
{
log.Info(String.Format("Order ({0}) was successfully exported"), order.ExternalOrderID);
return true;
}
return false;
}
catch (Exception e)
{
log.Error(String.Format("Exception occured while exporting order ({0})", order.ID), e);
return false;
}
}
Below is the code which does the actual http call. I marked the last functional line with the comment "The code successfully reach this line. After this nothing happens"
public Task<ResponseObject> SendOrder(SenderInformation sender, ReceiverInformation receiver)
{
OrderRequest request = new OrderRequest(sender, receiver);
return ExecuteRequest<OrderRequest, ResponseObject>(request);
}
private async Task<ResponseType> ExecuteRequest<RequestType, ResponseType> (RequestType request)
where RequestType : RequestObject
where ResponseType : class, ResponseObject, new()
{
try
{
using (var client = new HttpClient())
{
string xml = SerializeRequest(request);
HttpContent content = new StringContent(xml);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("text/xml");
string requestUrl = "URL";
HttpResponseMessage response = await client.PostAsync(requestUrl, content).ConfigureAwait(false);
// Parse response
if (response.IsSuccessStatusCode)
{
Stream responseStream = await response.Content.ReadAsStreamAsync();
ResponseType responseObject = DeserializeResponse<ResponseType>(responseStream);
if (responseObject != null)
{
responseObject.Success = true;
return responseObject; //The code successfully reach this line. After this nothing happens
}
else
{
log.Error("Response could not be deserialized");
}
}
else
{
log.Error("Error during request, got status code " + response.StatusCode);
}
}
}
catch (Exception e)
{
log.Error("Something went wrong!", e);
}
return new ResponseType() { Success = false };
}
The problem is on this line:
ExecuteTaskAsync(token, order).Wait();
This causes a deadlock: the awaits in the called method can't resume because the UI thread is blocked.
When you use async code, you must use it all the way; never wait synchronously for an async task to complete.

Categories