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;
}
Related
How can I write test cases for Generic Http methods. My methods are below.
public async Task<T> HttpGetAsync<T>(string urlSuffix)
{
var url = Common.FormatUrl(urlSuffix, _instanceUrl, _apiVersion);
return await HttpGetAsync<T>(url);
}
public async Task<T> HttpGetAsync<T>(Uri uri)
{
try
{
var response = await HttpGetAsync(uri);
var jToken = JToken.Parse(response);
if (jToken.Type == JTokenType.Array)
{
var jArray = JArray.Parse(response);
return JsonConvert.DeserializeObject<T>(jArray.ToString());
}
// else
try
{
var jObject = JObject.Parse(response);
return JsonConvert.DeserializeObject<T>(jObject.ToString());
}
catch
{
return JsonConvert.DeserializeObject<T>(response);
}
}
catch (BaseHttpClientException e)
{
throw ParseForceException(e.Message);
}
}
protected async Task<string> HttpGetAsync(Uri uri)
{
var responseMessage = await _httpClient.GetAsync(uri).ConfigureAwait(false);
if (responseMessage.StatusCode == HttpStatusCode.NoContent)
{
return string.Empty;
}
var response = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
if (responseMessage.IsSuccessStatusCode)
{
return response;
}
throw new BaseHttpClientException(response, responseMessage.StatusCode);
}
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
}
}
Basically I get
Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object occurred
when I try to get the token from the restservice.
when it gets to
public async Task<T> PostResponseLogin<T>(string weburl, FormUrlEncodedContent content) where T : class
{
var response = await client.PostAsync(weburl, content);
var jsonResult = response.Content.ReadAsStringAsync().Result;
var responseObject = JsonConvert.DeserializeObject<T>(jsonResult);
return responseObject;
}
on the return respondObject; it goes to the appDelegate and throws the exception.
-I'm currently in the learning process of C#/Xamarin, so if I have made a simple mistake that you notice, that will be why. Thanks for your help.
I've added MainPage = new NavigationPage(new LoginPage()); so I can navigate between pages, that is working.
edit: It was suggested that this might be a possible duplicate of another ticket with an unhandled exception. While it did help me to understand that error better, it's still more specific than that.
public class RestService
{
HttpClient client;
string grant_type = "password";
public RestService()
{
client = new HttpClient();
client.MaxResponseContentBufferSize = 256000;
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded' "));
}
public async Task<Token> Login(User user)
{
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("grant_type", grant_type));
postData.Add(new KeyValuePair<string, string>("Email", user.Email));
postData.Add(new KeyValuePair<string, string>("Password", user.Password));
var content = new FormUrlEncodedContent(postData);
var weburl = "https://blahblah.com/auth/login";
var response = await PostResponseLogin<Token>(weburl, content);
DateTime dt = new DateTime();
dt = DateTime.Today;
response.expireDate = dt.AddSeconds(response.expireIn);
return response;
}
public async Task<T> PostResponseLogin<T>(string weburl, FormUrlEncodedContent content) where T : class
{
var response = await client.PostAsync(weburl, content);
var jsonResult = response.Content.ReadAsStringAsync().Result;
var responseObject = JsonConvert.DeserializeObject<T>(jsonResult);
return responseObject;
}
public async Task<T> PostResponse<T>(string weburl, string jsonstring) where T : class
{
var Token = App.TokenDatabase.GetToken();
string ContentType = "application/json";
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Token.accessToken);
try
{
var Result = await client.PostAsync(weburl, new StringContent(jsonstring, Encoding.UTF8, ContentType));
if (Result.StatusCode == System.Net.HttpStatusCode.OK)
{
var JsonResult = Result.Content.ReadAsStringAsync().Result;
try
{
var ContentResp = JsonConvert.DeserializeObject<T>(JsonResult);
return ContentResp;
}
catch { return null; }
}
}
catch { return null; }
return null;
}
public async Task<T> GetResponse<T>(string weburl) where T : class
{
var Token = App.TokenDatabase.GetToken();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Token.accessToken);
try
{
var response = await client.GetAsync(weburl);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var JsonResult = response.Content.ReadAsStringAsync().Result;
try
{
var ContentResp = JsonConvert.DeserializeObject<T>(JsonResult);
return ContentResp;
}
catch
{
return null;
}
}
}
catch
{
return null;
}
return null;
}
}
I am handing HttpRequestException when I use PostAsync and it works fine, but when I am trying to handle same exception on GetAsync it throws TaskCanceledException a task was cancelled with a long timeout instead. How do I make GetAsync throw HttpRequestException?
public async Task<bool> AddQrCodeToRequest(int projectId, int requestId, string code, string token)
{
var data = JsonConvert.SerializeObject(new { code });
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var content = new StringContent(data, Encoding.UTF8, "application/json");
var result = await client.PostAsync(url, content);
if (result.IsSuccessStatusCode)
{
return true;
}
else
{
throw new Exception(CreateExceptionDescription(await result.Content.ReadAsStringAsync()));
}
}
public async Task<List<string>> GetUpdatedQrCodesList(Request request, string token)
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var result = await client.GetAsync(url);
if (result.IsSuccessStatusCode)
{
var requestsJson = await result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<List<string>>(requestsJson);
}
else
{
throw new Exception(CreateExceptionDescription(await result.Content.ReadAsStringAsync()));
}
}
handling post
try
{
string QrCode = result.Text;
if (await restService.AddQrCodeToRequest(Request, result.Text, Vars.User.Token))
{
QrCodes.Add(QrCode);
await DisplayAlert("Code added", QrCode, "OK");
}
}
catch (Exception ex)
{
if (ex is HttpRequestException)
{
//network ex handling
}
else
{
//other handling
}
}
handling get (app crashes after timeout)
try
{
UpdatedQrCodes = await restService.GetUpdatedQrCodesList(Request, Vars.User.Token);
}
catch (Exception ex)
{
if (ex is HttpRequestException)
{
//never thrown
}
else
{
//never also thrown
}
}
As a workaround use nuget Xamarin.Essentials and before you execute your GET check if there's internet available:
var current = Connectivity.NetworkAccess;
if (current == NetworkAccess.Internet)
{
// Connection to internet is available
}
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;
}