Requests start to fail in Xamarin using Refit - xamarin

After making a lot of network requests in a short space of time, we keep getting "A task was canceled" error from our responses, once we start getting these errors any requests made after the errors stop return the same errors too until we restart the app. We use an AuthenticatedHttpClientHandler to handle auth tokens in our requests:
public class AuthenticatedHttpClientHandler : HttpClientHandler
{
private readonly IReauthService _reauthService;
public AuthenticatedHttpClientHandler(IReauthService reauthService)
{
_reauthService = reauthService;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Debug.WriteLine("Url: " + request.RequestUri);
var auth = request.Headers.Authorization;
if (auth != null)
{
string token = await _reauthService.GetToken();
request.Headers.Authorization = new AuthenticationHeaderValue(auth.Scheme, token);
}
try
{
//Error occurs on this line
HttpResponseMessage resp = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
return resp;
}
catch (Exception e)
{
//this.GetLogger().LogToDebugConsole("FAILED: " + request.Method + " " + request.RequestUri);
//this.GetLogger().LogToDebugConsole("Reason: " + e.Message);
//this.GetLogger().LogToDebugConsole("Stacktrace: " + e.StackTrace);
Crashes.TrackError(e);
throw new RequestFailedException(e);
}
}
}
We use this like so:
container.RegisterSingleton(() =>
{
var handler = container.Resolve<AuthenticatedHttpClientHandler>();
HttpClient client = new HttpClient(handler)
{
BaseAddress = new Uri(UrlConfig.MobileApiBaseUrl + UrlConfig.Version),
Timeout = TimeSpan.FromSeconds(30)
};
var service = RestService.For<IMobileApiClient>(client);
return service;
});
The 30s timeout is not the issue as all our requests are under 2s and we can reproduce the errors consistently.
A single HttpClient instance is used for every call. We also use MvvmCross and the issue appears to only be on iOS. We have tried changing the HttpClientImplementation in our project settings, as well using a new instance of HttpClient in every call but neither have worked.
Full Stacktrace:
==========A task was canceled.==========
========== at System.Net.Http.MonoWebRequestHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x004ac] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/mcs/class/System.Net.Http/MonoWebRequestHandler.cs:507
at CustomerApp.Core.Networking.LoggingAuthenticatedHttpClientHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x0059f] in /Users/user/VSProjects/CustomerApp/CustomerApp.Core/Networking/AuthenticatedHttpClientHandler.cs:38 ==========
Build settings:

Related

How to call api vs parameters cancellationToken xamarin forms

I am building an operation that calls api with the cancellationToken parameter. cancel () from event A, when the party B is calling, the api will be caught, but I want this exception to be arrested on the api side. ..... help, it took me 4 days but it didn't work out
api server
public async Task<Actionresult> getText(CancellationToken token){ string s= "";try{for(int i=0;i<10;i++)
{
a += " number "+i;
await Task.Delay(1000,token);
}
}catch(Excaption ex){
return badrequest("canceled")
}
}
client xamaarin app
CancellationTokenSource cts = new CancellationTokenSource();
private async commandEventA()
{
cts.Cancel();
}
private async commandEventB()
{
var token =cts.Token();
string txtkq ="";
var client = new RestClient("urlwebb");
RestRequest request = new RestRequest("");
try{
var a = await
client.ExcuteTaskAsync(request,token);
}catch(TaskCancellationException ex){}
}

Calling an async api from a xamarin application

I am working on a xamarin mobile application, upon making an async call to the exposed api, i do not get any error, however when i execute the .Result on the task the call never proceeds and it stuck forever.
Click here to see stringResourceResponse details
The same .Result call from a separate project (windows service) in the same solution works.
Any idea if .NET standard is causing limitation in executing async tasks, any advice would be helpful, thanks
Code added below:
//This is code from app.xaml.cs
var stringResourceApi = new StringResourceApiTask();
Task.Run(() =>
{
a = controller.CallStringResourceApi(stringResourceApi);
}).Wait();
public class MobileController
{
public string CallStringResourceApi(StringResourceApiTask stringResourceApiTask)
{
return stringResourceApiTask.Start(StringResourceUrl);
}
}
public override string Start(string URL)
{
var stringResourceResponse = SendRequest(url, "", HttpMethod.Get);
var result = stringResourceResponse.Result;
return result;
}
protected async Task < string > SendRequest(string url, string uri, HttpMethod method, int attempt = 1, int maxAttempts = 5)
{
return await SendRequest(
url, uri, Key, Secret, method, string.Empty, attempt, maxAttempts)
.ConfigureAwait(false);
}
protected async Task<string> SendRequest(string url, string uri, string key, string secret, HttpMethod method,
string requestBody = "", int attempt = 1, int maxAttempts = 5)
{
if (attempt > maxAttempts)
{
return null;
}
var client = InitialiseHttpClient(key, secret);
var request = new HttpRequestMessage
{
RequestUri = string.IsNullOrEmpty(url) ? new Uri(uri) : new Uri(url),
Method = method,
};
if (!string.IsNullOrWhiteSpace(requestBody))
{
request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
}
SetOutputText($"Attempting to communicate with {uri}...{Environment.NewLine}");
using (var response = await client.SendAsync(request).ConfigureAwait(false))
{
using (var content = response.Content)
{
try
{
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex)
{
if (attempt > maxAttempts)
{
SetOutputText(errorMessage);
}
return await SendRequest(url, uri, key, secret, method, requestBody, attempt + 1).ConfigureAwait(false);
}
var responseBody = await content.ReadAsStringAsync().ConfigureAwait(false);
var isSuccessResponseButEmptyBody = response.IsSuccessStatusCode &&
(string.IsNullOrEmpty(responseBody) ||
string.IsNullOrWhiteSpace(responseBody));
if (!isSuccessResponseButEmptyBody)
{
return responseBody;
}
if (attempt > maxAttempts)
{
SetOutputText(errorMessage);
}
return await SendRequest(url, uri, key, secret, method, requestBody, attempt + 1).ConfigureAwait(false);
}
}
}
when i execute the .Result on the task the call never proceeds and it stuck forever.
Yes. This is a common deadlock situation. When code running on the UI thread blocks on asynchronous code, a deadlock usually occurs.
The same .Result call from a separate project (windows service) in the same solution works.
It works because the Win32 service code does not run on a UI thread.
The proper solution is to remove the blocking code; use await instead. This in turn will cause the calling methods to become async (e.g., StringResourceApiTask.Start), and they should also be awaited, etc. The usage of async and await should "grow" through your code; this is natural.
Alternatively, you can block in a thread pool thread, e.g., Task.Run(() => a = controller.CallStringResourceApi(stringResourceApi)).GetAwaiter().GetResult();. This is a bit of a hack (consuming an unnecessary thread), but it's a quick way to remove the deadlock. Note that this hack is not appropriate for ASP.NET apps; it's acceptable here since this is a UI app.

Blazor Startup Error: System.Threading.SynchronizationLockException: Cannot wait on monitors on this runtime

I am trying to call an api during the blazor(client side) startup to load language translations into the ILocalizer.
At the point I try and get the .Result from the get request blazor throws the error in the title.
This can replicated by calling this method in the program.cs
private static void CalApi()
{
try
{
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(#"https://dummy.restapiexample.com/api/v1/employees");
string path = "ididcontent.json";
string response = httpClient.GetStringAsync(path)?.Result;
Console.WriteLine(response);
}
catch(Exception ex)
{
Console.WriteLine("Error getting api response: " + ex);
}
}
Avoid .Result, it can easily deadlock. You get this error because the mechanism is not (cannot be) supported on single-threaded webassembly. I would consider it a feature. If it could wait on a Monitor it would freeze.
private static async Task CalApi()
{
...
string response = await httpClient.GetStringAsync(path);
...
}
All events and lifecycle method overrides can be async Task in Blazor, so you should be able to fit this in.
In Program.cs
public static async Task Main(string[] args)
{
......
builder.Services.AddSingleton<SomeService>();
var host = builder.Build();
...
call your code here but use await
var httpClient = host.Services.GetRequiredService<HttpClient>();
string response = await httpClient.GetStringAsync(path);
...
var someService = host.Services.GetRequiredService<SomeService>();
someService.SomeProperty = response;
await host.RunAsync();
This is a example best:
var client= new ProductServiceGrpc.ProductServiceGrpcClient(Channel);
category = (await client.GetCategoryAsync(new GetProductRequest() {Id = id})).Category;

dealing with an error from a typed WebAPI HttpClient call

I have a number of calls I make to a webapi client which return a Task
something like this
public async Task<TResp> GetMyThingAsync(TReq req)
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(BaseURI);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/xml"));
await HttpRuntime.Cache.GetToken().ContinueWith((t) =>
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("XXX", t.Result);
});
var httpResponseMessage = await client.PostAsXmlAsync<TReq>("This/That/", req);
httpResponseMessage.EnsureSuccessStatusCode();
var resp = await httpResponseMessage.Content.ReadAsAsync<TResp>();
return resp;
}
}
the calls to the api can of course return 500's or some other problem. EnsureSuccessStatusCode() obviously throws if something like that happens, but then its too late to do anything with any information in the response.
is there a nice way of dealing with this?
I understand you can add a messageHandler with the client, something like
HttpClient client = HttpClientFactory(new ErrorMessageHandler()) ..
var customHandler = new ErrorMessageHandler()
{ InnerHandler = new HttpClientHandler()};
HttpClient client = new HttpClient(customHandler);
is this the way to go? what would the ErroMessageHandler look like and do to return something useful to the calling controller...?
thanks muchly
nat
Creating a custom handler can be an elegant solution to go about logging the exception or validating the response. I am not sure if the called controller is waiting for a meaningful response from the clients end if it encounters an exception. I think the real important part is to make sure you (the client) handle the web apis errors gracefully.
This can be done in a couple of ways:
You can handle exceptions locally inside the calling method. You can use the HttpResponseMessage.IsSuccessStatusCode property which indicated if a bad response returned instead of calling httpResponseMessage.EnsureSuccessStatusCode() which throws an exception, and return a custom ErrorResponse (or do whatever you decide):
var client = new HttpClient() // No need to dispose HttpClient
client.BaseAddress = new Uri(BaseURI);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/xml"));
var token = await HttpRuntime.Cache.GetToken();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("XXX", token);
var httpResponseMessage = await client.PostAsXmlAsync<TReq>("This/That/", req);
if (!httpResponseMessage.IsSuccessStatusCode)
{
return Task.FromResult(new ErrorResponse()) // Create some kind of error response to indicate failure
}
var resp = await httpResponseMessage.Content.ReadAsAsync<TResp>();
return resp;
Create a ErrorLoggingHandler which can log exceptions (or do something else) received from the web api:
public class ErrorLoggingHandler : DelegatingHandler
{
private readonly StreamWriter _writer; // As a sample, log to a StreamWriter
public ErrorLoggingHandler(Stream stream)
{
_writer = new StreamWriter(stream);
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
// This would probably be replaced with real error
// handling logic (return some kind of special response etc..)
_writer.WriteLine("{0}\t{1}\t{2}", request.RequestUri,
(int) response.StatusCode, response.Headers.Date);
}
return response;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_writer.Dispose();
}
base.Dispose(disposing);
}
}
Then, you can create your HttpClient using HttpClientFactory:
var httpclient = HttpClientFactory.Create(new ErrorLoggingHandler(new FileStream(#"Location", FileMode.OpenOrCreate)));

how to perform post method in windows 8 metro?

I have followed the HttpClient samples but couldn't figure it out how to post a method with 2 parameters.
Below is what I tried but it return bad gateway error:
private async void Scenario3Start_Click(object sender, RoutedEventArgs e)
{
if (!TryUpdateBaseAddress())
{
return;
}
Scenario3Reset();
Scenario3OutputText.Text += "In progress";
string resourceAddress = "http://music.api.com/api/search_tracks";
try
{
MultipartFormDataContent form = new MultipartFormDataContent();
// form.Add(new StringContent(Scenario3PostText.Text), "data");
form.Add(new StringContent("Beautiful"), "track");
form.Add(new StringContent("Enimem"), "artist");
HttpResponseMessage response = await httpClient.PostAsync(resourceAddress, form);
}
catch (HttpRequestException hre)
{
Scenario3OutputText.Text = hre.ToString();
}
catch (Exception ex)
{
// For debugging
Scenario3OutputText.Text = ex.ToString();
}
}
I looked all over the internet, but couldn't find any working examples or documents that show how to perform the http post method. Any materials or samples would help me a lot.
Try FormUrlEncodedContent instead of MultipartFormDataContent:
var content = new FormUrlEncodedContent(
new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("track", "Beautiful"),
new KeyValuePair<string, string>("artist", "Enimem")
}
);
I prefer to take the following approach where you set the POST data into the request content body. Having to debug it is much easier!
Create your HttpClient object with the URL you're posting to:
string oauthUrl = "https://accounts.google.com/o/oauth2/token";
HttpClient theAuthClient = new HttpClient();
Form your request with the Post method to your url
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, oauthUrl);
Create a content string with your parameters explicitly set in POST data format and set these in the request:
string content = "track=beautiful" +
"&artist=eminem"+
"&rating=explicit";
request.Method = HttpMethod.Post;
request.Content = new StreamContent(new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(content)));
request.Content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
Send the request and get a response:
try
{
HttpResponseMessage response = await theAuthClient.SendAsync(request);
handleResponse(response);
}
catch (HttpRequestException hre)
{
}
Your handler will be called once the request returns and will have response data from your POST. The following example shows a handler that you could put a breakpoint into to see what the response content is, at that point, you could parse it or do whatever you need to do with it.
public async void handleResponse(HttpResponseMessage response)
{
string content = await response.Content.ReadAsStringAsync();
if (content != null)
{
// put your breakpoint here and poke around in the data
}
}

Resources