GetStringAsync method call hangs the UI and never completes - c#

I am trying to access my url using GetStringAsync method but the call hangs and never ends. When I check it in browser it works perfect but somehow in my code it never ends. My code:
public bool Login(string url,string userName, string password)
{
try
{
Task<LoginResponse> response = GetLoginData(url, userName, password);
if(response.Result.UserInfo.UserId > 0){
IsAuthenticated = true;
}
return IsAuthenticated;
}
catch (ArgumentException argex)
{
ErrorMessage = argex.Message;
IsAuthenticated = false;
return IsAuthenticated;
}
}
public async Task<LoginResponse> GetLoginData(string url, string userName, string password)
{
try{
var param = new AuthenticationParam();
param.UserName = userName;
param.Password = password;
var response = await LoginValidate(url, param);
response = response.Replace("\"", string.Empty);
response = response.Replace("\\", "\"");
LoginResponse list = JsonConvert.DeserializeObject<LoginResponse>(
response, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
return list;
}
catch(Exception e){
System.Diagnostics.Debug.WriteLine(e);
return new LoginResponse();
}
}
private static async Task<string> LoginValidate(string url, AuthenticationParam param){
try
{
string result = "";
using (var client = new HttpClient())
{
var json_data = string.Empty;
json_data = JsonConvert.SerializeObject(param);
var byteArray = Encoding.UTF8.GetBytes(json_data);
var base64 = Convert.ToBase64String(byteArray);
url = $"{url}{base64}";
result = await client.GetStringAsync(url);
return result;
}
}
catch (Exception ex)
{
return "Error: " + ex.Message;
}
}
It hangs at line result = await client.GetStringAsync(url);

if(response.Result.UserInfo.UserId > 0){ -> .Result is a blocking call and can lead to deadlocks. Use await all the way up the call chain.
public async Task<bool> Login(string url,string userName, string password)
{
try
{
var response = await GetLoginData(url, userName, password);
if(response.UserInfo.UserId > 0){
IsAuthenticated = true;
}
return IsAuthenticated;
}
catch (ArgumentException argex)
{
ErrorMessage = argex.Message;
IsAuthenticated = false;
return IsAuthenticated;
}
}
Up in the call stack use var success = await Login(...);
And please, read this excellent blogpost for the background about how this deadlock is created.

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 to wait multiple threads while a common task is being carried out?

I have multiple threads executing API calls parallelly. Whenever the jwt token expires i want all my threads to wait until the refresh token API is called and returns with valid updated jwt token. I have a singleton class which has a refresh token method which will make the call and update the token. How can I make sure that all my other threads will wait until token fetch is complete?
public class JWTTokenManager
{
private static JWTTokenManager _tokenManager;
private string _token;
private bool _refreshingToken;
public static JWTTokenManager GetManager()
{
if (_tokenManager == null)
_tokenManager = new JWTTokenManager();
return _tokenManager;
}
public void UpdateToken(string token)
{
_token = token;
}
public string GetToken()
{
return _token;
}
public async Task<bool> ValidateRefreshTocken()
{
UserInfo userdata = JsonConvert.DeserializeObject<UserInfo>(GetUserInfo(_token), new Helper.DefaultJsonSetting());
if (!string.IsNullOrWhiteSpace(userdata.Exp) && TokenExpired(long.Parse(userdata.Exp)))
{
_refreshingToken = true;
JWTToken jwtToken = Database.DBService.GetDB().FetchJWTToken();
RefreshToken requestRefresh = new RefreshToken
{
ExpiredTocken = jwtToken.Token,
RefreshTocken = jwtToken.RefreshToken
};
HttpClient httpClient = CloudService.GetCloudService().GetHttpClient();
HttpResponseMessage response = await httpClient.PostAsync($"account/v1/tokenRefresh", new StringContent(JsonConvert.SerializeObject(requestRefresh), Encoding.UTF8, "application/json"));
bool responseStatus = await ParseTokenResponseAsync(response);
_refreshingToken = false;
return responseStatus;
}
else
{
return true;
}
}
private string GetUserInfo(string key)
{
string[] base64Url = key.Split('.');
if (base64Url.Length > 1)
{
string userinfo = base64Url[1];
userinfo = userinfo.Replace(" ", "+");
int mod4 = userinfo.Length % 4;
if (mod4 > 0)
{
userinfo += new string('=', 4 - mod4);
}
var base64EncodedBytes = System.Convert.FromBase64String(userinfo);
return Encoding.UTF8.GetString(base64EncodedBytes);
}
else
{
return "";
}
}
public bool TokenExpired(long unixTimeStamp)
{
DateTime tokenExpiryDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
tokenExpiryDateTime = tokenExpiryDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
DateTime currentDateTime = DateTime.Now;
return DateTime.Compare(tokenExpiryDateTime, currentDateTime) <= 0;
}
public async Task<bool> ParseTokenResponseAsync(HttpResponseMessage httpResponse)
{
if (httpResponse.IsSuccessStatusCode == true)
{
string responseString = await httpResponse.Content.ReadAsStringAsync();
Newtonsoft.Json.Linq.JObject responsedataObject = Newtonsoft.Json.Linq.JObject.Parse(responseString);
string token = responsedataObject["data"]["token"].ToString();
string refreshToken = responsedataObject["data"]["refreshToken"].ToString();
_token = token;
JWTToken updatedToken = new JWTToken()
{
Token = _token,
RefreshToken = refreshToken
};
Database.DBService.GetDB().InsertOrUpdateJWTToken(updatedToken);
return true;
}
else
{
return false;
}
}
}
public class CloudService
{
private const int TIME_OUT = 50;
private const int HTTP_GET = 0;
private const int HTTP_PUT = 1;
private const int HTTP_POST = 2;
private const int HTTP_DELETE = 3;
private static CloudService _serviceInstance;
public static CloudService GetCloudService()
{
if (_serviceInstance == null)
_serviceInstance = new CloudService();
return _serviceInstance;
}
private async Task<HttpResponseMessage> ExecuteHttpTask(int taskType, string url, StringContent content = null)
{
HttpClient httpClient = GetHttpClient();
switch (taskType)
{
case HTTP_GET:
return await httpClient.GetAsync(url);
case HTTP_PUT:
return await httpClient.PutAsync(url, content);
case HTTP_POST:
return await httpClient.PostAsync(url, content);
case HTTP_DELETE:
return await httpClient.DeleteAsync(url);
default:
return null;
}
}
public async Task<Response> HTTPTask(string url, int taskType, StringContent content = null, bool login = false)
{
bool refreshTocken = await JWTTokenManager.GetManager().ValidateRefreshTocken();
Response httpResponse = new Response();
try
{
HttpResponseMessage response = await ExecuteHttpTask(taskType, url, content);
string responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
httpResponse.status = "error";
else
httpResponse.status = "data";
httpResponse.data = ParseResponseData(httpResponse.status, responseString);
}
catch (Exception e)
{
httpResponse = GenericErrorResponse(e.Message);
}
return httpResponse;
}
public async Task<Response> GetSectionAsync(string id)
{
string url = $"catalog/v2/homepageSections/{id}?order-by=name,asc";
return await HTTPTask(url, Constants.HTTP_GET);
}
public async Task<Response> GetProductAsync(string id)
{
string url = $"catalog/v2/products/{id}";
return await HTTPTask(url, Constants.HTTP_GET);
}
public async Task<Response> GetCourseDetailsAsync(string id)
{
string url = $"catalog/v2/products/{id}/courseDetails";
return await HTTPTask(url, Constants.HTTP_GET);
}
}
Different threads will call methods in ClouService which in turn calls different APIs parallelly and all these go through the HTTPTask method, where the token is validated and if not valid, API is called to get the updated token. How can I make all the APIs(the threads) to wait from the moment when token is invalid and until the API returns valid token?
Based on the comments I have updated both the classes. Please have a look.
public sealed class JWTTokenManager
{
private static readonly JWTTokenManager _tokenManager = new JWTTokenManager();
private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private string _token;
// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static JWTTokenManager()
{
}
private JWTTokenManager()
{
}
public static JWTTokenManager GetManager()
{
return _tokenManager;
}
public void UpdateToken(string token)
{
_token = token;
}
public string GetToken()
{
return _token;
}
public async Task<bool> ValidateRefreshTocken(bool forceRefresh = false)
{
bool validToken;
await _semaphore.WaitAsync();
try
{
UserInfo userdata = JsonConvert.DeserializeObject<UserInfo>(GetUserInfo(_token), new DefaultJsonSetting());
if (forceRefresh || !string.IsNullOrWhiteSpace(userdata.Exp) && TokenExpired(long.Parse(userdata.Exp)))
{
validToken = await RefreshToken();
}
else
{
validToken = true;
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
validToken = false;
}
finally
{
_semaphore.Release();
}
return validToken;
}
private async Task<bool> RefreshToken()
{
JWTToken jwtToken = Database.DBService.GetDB().FetchJWTToken();
RefreshToken requestRefresh = new RefreshToken
{
ExpiredTocken = jwtToken.Token,
RefreshTocken = jwtToken.RefreshToken
};
HttpClient httpClient = CloudService.GetCloudService().GetHttpClient();
HttpResponseMessage response = await httpClient.PostAsync($"account/v1/tokenRefresh", new StringContent(JsonConvert.SerializeObject(requestRefresh), Encoding.UTF8, "application/json"));
bool status = await ParseTokenResponseAsync(response);
return status;
}
private string GetUserInfo(string key)
{
string[] base64Url = key.Split('.');
if (base64Url.Length > 1)
{
string userinfo = base64Url[1];
userinfo = userinfo.Replace(" ", "+");
int mod4 = userinfo.Length % 4;
if (mod4 > 0)
{
userinfo += new string('=', 4 - mod4);
}
var base64EncodedBytes = System.Convert.FromBase64String(userinfo);
return Encoding.UTF8.GetString(base64EncodedBytes);
}
else
{
return "";
}
}
public bool TokenExpired(long unixTimeStamp)
{
DateTime tokenExpiryDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
tokenExpiryDateTime = tokenExpiryDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
DateTime currentDateTime = DateTime.Now;
return DateTime.Compare(tokenExpiryDateTime, currentDateTime) <= 0;
}
public async Task<bool> ParseTokenResponseAsync(HttpResponseMessage httpResponse)
{
if (httpResponse.IsSuccessStatusCode == true)
{
string responseString = await httpResponse.Content.ReadAsStringAsync();
Newtonsoft.Json.Linq.JObject responsedataObject = Newtonsoft.Json.Linq.JObject.Parse(responseString);
string token = responsedataObject["data"]["token"].ToString();
string refreshToken = responsedataObject["data"]["refreshToken"].ToString();
_token = token;
JWTToken updatedToken = new JWTToken()
{
Token = _token,
RefreshToken = refreshToken
};
Database.DBService.GetDB().InsertOrUpdateJWTToken(updatedToken);
return true;
}
else
{
return false;
}
}
}
public sealed class CloudService
{
private const int TIME_OUT = 50;
private const int HTTP_GET = 0;
private const int HTTP_PUT = 1;
private const int HTTP_POST = 2;
private const int HTTP_DELETE = 3;
private static readonly CloudService _serviceInstance = new CloudService();
static CloudService()
{
}
private CloudService()
{
}
public static CloudService GetCloudService()
{
return _serviceInstance;
}
public HttpClient GetHttpClient()
{
HttpClient httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(TIME_OUT),
BaseAddress = new Uri($"{AppConst.ServerBaseURL}/"),
};
httpClient.DefaultRequestHeaders.Add("X-Jwt-Token", JWTTokenManager.GetManager().GetToken());
httpClient.DefaultRequestHeaders.Add("tenantId", AppConst.TenanatID);
return httpClient;
}
private async Task<HttpResponseMessage> ExecuteHttpTask(int taskType, string url, StringContent content = null)
{
HttpClient httpClient = GetHttpClient();
switch (taskType)
{
case HTTP_GET:
return await httpClient.GetAsync(url);
case HTTP_PUT:
return await httpClient.PutAsync(url, content);
case HTTP_POST:
return await httpClient.PostAsync(url, content);
case HTTP_DELETE:
return await httpClient.DeleteAsync(url);
default:
return null;
}
}
public async Task<Response> HTTPTask(string url, int taskType, StringContent content = null, bool login = false)
{
Response httpResponse = new Response();
try
{
HttpResponseMessage response = await ExecuteHttpTask(taskType, url, content);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized && !login)
{
bool refreshTocken = await JWTTokenManager.GetManager().ValidateRefreshTocken(true);
if (refreshTocken == true)
{
response = await ExecuteHttpTask(taskType, url, content);
}
else
{
httpResponse = GenericErrorResponse();
return httpResponse;
}
}
string responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
httpResponse.status = "error";
else
httpResponse.status = "data";
httpResponse.data = ParseResponseData(httpResponse.status, responseString);
}
catch (Exception e)
{
httpResponse = GenericErrorResponse(e.Message);
}
return httpResponse;
}
}
You can use a ManualResetEvent. Let's say you have three threads A, B (calling api calls in parallel) and C (performing token refresh).
private static ManualResetEvent mre = new ManualResetEvent(true); // event is always set except when refreshing the token
Code on thread A,B
mre.WaitOne() // it blocks whenever the event is reset
...
Code on thread C
mre.Reset() // this blocks all the waiting threads
perform the token refresh
mre.Set() // this frees all the waiting threads
You should make JWTTokenManager return a token instead of it needs a token.
If the token is valid, it will return a completed task with the valid token, if it's not, it will return a task that will complete when the token is retrieved. The same task can be awaited by multiple concurrent threads.
public class JWTTokenManager
{
private Task<string> tokenTask;
private readonly object sync = new object();
public Task<string> GetTokenAsync()
{
lock (sync)
{
if (tokenTask.IsCompleted && !IsTokenValid(tokenTask.Result))
{
tokenTask = GetNewTokenAsync();
}
return tokenTask;
}
}
}
Lock-free version:
public class JWTTokenManager
{
private Task<string> tokenTask;
private readonly object sync = new object();
public Task<string> GetTokenAsync()
{
var currentTokenTask = Volatile.Read(ref tokenTask);
if (currentTokenTask .IsCompleted && !IsTokenValid(currentTokenTask .Result))
{
currentTokenTask = GetNewTokenAsync();
Volatile.Write(ref tokenTask, currentTokenTask);
}
return currentTokenTask ;
}
}

Mock returns null value when ReturnResult is a custom object but works as expected when it is a primitive type bool

I am using Moq in .net core(1.1) and having a bit of torrid time understanding this behavior as all the examples on interweb points to the fact the this should work with no issues.
I have already tried with:
Returns(Task.FromResult(...)
Returns(Task.FromResult(...)
ReturnsAsync(...)
Mocking a IHttpClient interface to wrap PostAsync, PutAsync and GetAsync. All of these return an ApiResponse object.
var mockClient = new Mock<IHttpClient>();
Does not work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(new ApiResponse()));
PostSync definition:
public async Task<ApiResponse> PostAsync(string url, string body, string authToken = null)
Does work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(bool));
PostSync definition:
public async Task<bool> PostAsync(string url, string body, string authToken = null)
Usage:
var api = new ApiService(mockClient.Object);
var response = api.LoginAsync(body.Username, body.Password);
UPDATE
[Fact]
public async void TestLogin()
{
var mockClient = new Mock<IHttpClient>();
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null)).Returns(Task.FromResult(new ApiResponse()));
var api = new ApiService(mockClient.Object);
var response = await api.LoginAsync(body.Username, body.Password);
Assert.IsTrue(response);
}
Return Type:
public class ApiResponse
{
public string Content { get; set; }
public HttpStatusCode StatusCode { get; set; }
public string Reason { get; set; }
}
LoginAsync:
public async Task<bool> LoginAsync(string user, string password)
{
var body = new { Username = user, Password = password };
try
{
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response .State == 1;
}
catch (Exception ex)
{
Logger.Error(ex);
return false;
}
}
PostAsync:
public async Task<object> PostAsync(string url, string body, string authToken = null)
{
var client = new HttpClient();
var content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.PostAsync(new Uri(url), content);
var resp = await response.Result.Content.ReadAsStringAsync();
return new ApiResponse
{
Content = resp,
StatusCode = response.Result.StatusCode,
Reason = response.Result.ReasonPhrase
};
}
Assuming a simple method under test like this based on minimal example provided above.
public class ApiService {
private IHttpClient _http;
private string _login_url;
public ApiService(IHttpClient httpClient) {
this._http = httpClient;
}
public async Task<bool> LoginAsync(string user, string password) {
var body = new { Username = user, Password = password };
try {
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response.StatusCode == HttpStatusCode.OK;
} catch (Exception ex) {
//Logger.Error(ex);
return false;
}
}
}
The following test works when configured correctly
[Fact]
public async Task Login_Should_Return_True() { //<-- note the Task and not void
//Arrange
var mockClient = new Mock<IHttpClient>();
mockClient
.Setup(x => x.PostAsync(It.IsAny<string>(), It.IsAny<string>(), null))
.ReturnsAsync(new ApiResponse() { StatusCode = HttpStatusCode.OK });
var api = new ApiService(mockClient.Object);
//Act
var response = await api.LoginAsync("", "");
//Assert
Assert.IsTrue(response);
}
The above is just for demonstrative purposes only to show that it can work provided the test is configured properly and exercised based on the expected behavior.
Take some time and review the Moq quick start to get a better understanding of how to use the framework.

How to post a webapi call using async and await

I am trying write a webapi which tries to post a webapi call using async and await,my current issue is as soon as I call await client.PostAsync(url, content); it hangs.
1.How to debug why it is hanging?
2.Is there a way to do it without async and await?I want to do it sequentially
public static async Task<string> testWCF2(string xmlConfig)
{
string submitOut;
using (var client = new System.Net.Http.HttpClient())
{
var url = "http://server:8100/api/SoftwareProductBuild";
var content = new StringContent(xmlConfig, Encoding.UTF8, "application/xml");
var response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
submitOut = responseBody;
}
else
{
submitOut = string.Format("Bad Response {0} \n", response.StatusCode.ToString());
submitOut = submitOut + response;
}
}
return submitOut;
}
public async Task<string> QlasrSubmit(List<XMLSiInfo> xmlConfigs)
{
string submitOut = "QLASR: ";
foreach (XMLSiInfo xmlConfig in xmlConfigs)
{
submitOut = submitOut + "\n" + await testWCF2(xmlConfig.xml);
}
return submitOut;
}
public async Task<string> QlasrPostcommit(string si, string sp, string variant = null)
{
.....
string submitStatus = await QlasrSubmit(siInfo);
.....
return submitStatus;
}
Service:
public async Task<string> QlasrPostcommit(string si, string sp, string variant = null)
{
return await DPR.QlasrPostcommit(si, sp, variant);
}
Controller:
[Route("api/DevPool/QlasrPostcommit")]
[HttpPost]
public ResponseObject QlasrPostcommit(string si, string sp, string variant = null)
{
ResponseObject response = new ResponseObject();
try
{
response.status = 200;
response.data = DPS.QlasrPostcommit(si, sp, variant);
return response;
}
catch (Exception e)
{
response.status = 200;
response.data = null;
response.message = e.Message;
return response;
}
}
You should use async all the way, as I mentioned in your previous question:
[Route("api/DevPool/QlasrPostcommit")]
[HttpPost]
public async Task<ResponseObject> QlasrPostcommit(string si, string sp, string variant = null)
{
ResponseObject response = new ResponseObject();
try
{
response.status = 200;
response.data = await DPS.QlasrPostcommit(si, sp, variant);
return response;
}
catch (Exception e)
{
response.status = 200;
response.data = null;
response.message = e.Message;
return response;
}
}
In this particular case, you're running into a deadlock because you're blocking on asynchronous code.
I solved it and it works perfectly, without deallock and with waiting result!!
You have fix the Service:
public string QlasrPostcommit(string si, string sp, string variant = null)
{
Task<string > task = Task.Run<string >(async () => await
DPR.QlasrPostcommit(si, sp, variant));
task.Result;
}
Generic answer:
public TypeToReturn MyAsyncMethod(myParams...)
{
Task<TypeToReturn> task = Task.Run<TypeToReturn>(async () => await
MyAsyncMethod(myParams...));
task.Result;
}

Azure Mobile Service .NET Backend HttpResponseMessage return null Content

I had this function in the web api of the mobile service declared
public HttpResponseMessage Post(LoginRequest loginRequest)
{
EasyParkContext context = new EasyParkContext();
User user = context.Users.SingleOrDefault(a => a.UserName == loginRequest.UserName);
if (user != null)
{
if (BCrypt.Net.BCrypt.Verify(loginRequest.Password, user.Password))
{
ClaimsIdentity claimsIdentity = new ClaimsIdentity();
claimsIdentity.AddClaim(new Claim(ClaimTypes.PrimarySid, user.Id));
claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, loginRequest.UserName));
LoginResult loginResult = new EasyParkLoginProvider(handler).CreateLoginResult(claimsIdentity, Services.Settings.MasterKey);
return this.Request.CreateResponse(HttpStatusCode.OK, loginResult);
}
}
return this.Request.CreateResponse(HttpStatusCode.Unauthorized, "Invalid username or password");
}
And this function declared in my client
public async Task<bool> Login(string userName, string password)
{
LoginRequest loginRequest = new LoginRequest() { UserName = userName, Password = password };
try
{
HttpResponseMessage loginResult = await _service.InvokeApiAsync<LoginRequest, HttpResponseMessage>("EasyParkLogin", loginRequest);
JObject json = JObject.Parse(loginResult.Content.ToString());
_service.CurrentUser = new MobileServiceUser(json["user"]["userId"].ToString().Replace("EasyPark:", ""))
{
MobileServiceAuthenticationToken = json["authenticationToken"].ToString()
};
return true;
}
catch (Exception e)
{
return false;
}
}
It works at the mobile service page, but is returning null during my client code, sorry I can't attached the image due to my reputation...
public async Task<bool> Login(string userName, string password)
{
LoginRequest loginRequest = new LoginRequest() { UserName = userName, Password = password };
try
{
var loginResult = await _service.InvokeApiAsync("EasyParkLogin", JToken.FromObject(loginRequest));
JObject json = JObject.Parse(loginResult.ToString());
_service.CurrentUser = new MobileServiceUser(json["user"]["userId"].ToString().Replace("EasyPark:", ""))
{
MobileServiceAuthenticationToken = json["authenticationToken"].ToString()
};
return true;
}
catch (Exception e)
{
return false;
}
}
by changing the from the code:
HttpResponseMessage loginResult = await _service.InvokeApiAsync<LoginRequest, HttpResponseMessage>("EasyParkLogin", loginRequest);
JObject json = JObject.Parse(loginResult.Content.ToString());
to
var loginResult = await _service.InvokeApiAsync("EasyParkLogin", JToken.FromObject(loginRequest));
JObject json = JObject.Parse(loginResult.ToString());
try to check the return value of
await _service.InvokeApiAsync<LoginRequest, HttpResponseMessage>("EasyParkLogin", loginRequest);

Categories