Xamarin app deadlocks when making api call - c#

My Xamarin app deadlocks when trying to make API call (asp.net core web API). The mobile app is using Android emulator. I created both API and client app following Microsoft's own tutorial. Requests run normally when making call with Postman or directly in the browser of my dev machine.
Constants class:
public static class Constants
{
public static string BaseAddress =
DeviceInfo.Platform == DevicePlatform.Android
? "https://10.0.2.2:44348"
:"https://localhost:44348";
public static string AppointmentsUrl = $"{BaseAddress}/api/appointments/";
}
Handler:
public class AppointmentHandler
{
HttpClient client;
JsonSerializerOptions serializerOptions;
private List<Appointment> Appointments { get; set; }
public AppointmentHandler()
{
client = new HttpClient();
serializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
}
public async Task<List<Appointment>> GetAppointments()
{
Appointments = new List<Appointment>();
try
{
Uri uri = new Uri(string.Format(Constants.AppointmentsUrl, string.Empty));
// the program deadlocks here
HttpResponseMessage response = await this.client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<List<Appointment>>(content, serializerOptions);
}
}
catch (Exception e)
{
var m = e.Message;
}
return null;
}
}

Have you tried using
.ConfigureAwait(false)
This is a common issue with async code and user interfaces. More here:
https://forums.xamarin.com/discussion/174173/should-i-use-configureawait-bool-or-not

Related

Xamarin Forms and Asp.Net Web Api

I have been following a youtube tutorial on connecting my a xamarin forms app to an asp.net web api. Unfortunately my Listview is not getting populated by data from the api.
The Xamarin forms app has the following Files:
RestClient.cs
public class RestClient<T> {
private const string WebServiceUrl = "http://localhost:49864/api/Oppotunities/";
public async Task<List<T>> GetAsync()
{
var httpClient = new HttpClient();
var json = await httpClient.GetStringAsync(WebServiceUrl);
var OppotunityList = JsonConvert.DeserializeObject<List<T>>(json);
return OppotunityList ;
} }
MainViewModel.cs
public MainViewModel()
{
InitializeDataAsync();
}
private async Task InitializeDataAsync()
{
var oppotunitiesServices = new OppotunitiesServices();
OppotunitiesList = await oppotunitiesServices.GetOppotunitiesAsync();
}
OppotunityServices.cs
public class OppotunitiesServices
{
public async Task<List<Oppotunity>> GetOppotunitiesAsync()
{
RestClient<Oppotunity> restClient = new RestClient<Oppotunity >();
var oppotunitiesList = await restClient.GetAsync();
return oppotunitiesList;
}
}
If you are debugging from an emulator, you should not use localhost to reach your development machine. You have to use the IP address or your running service.
You can test IP addresses directly from the browser of your emulator so you don't waste time starting/stopping your app to debug this...
Hope it helps

Can't get a valid response from a REST web service using System.Net.Http.HttpClient

I am using this test method (and helper class) to verify the response from an external web service:
[TestMethod]
public void WebServiceReturnsSuccessResponse()
{
using (var provider = new Provider(new Info()))
using (var result = provider.GetHttpResponseMessage())
{
Assert.IsTrue(result.IsSuccessStatusCode);
}
}
private class Info : IInfo
{
public string URL { get; set; } =
"https://notreallythe.website.com:99/service/";
public string User { get; set; } = "somename";
public string Password { get; set; } = "password1";
}
I can't get this test to pass; I always get a 500 - Internal Server Error result. I have connected via an external utility (Postman) - so the web service is up and I can connect with the url & credentials that I have.
I think the problem is in my instantiation of the HttpClient class, but I can't determine where. I am using Basic authentication:
public class Provider : IProvider, IDisposable
{
private readonly HttpClient _httpClient;
public Provider(IInfo config){
if (config == null)
throw new ArgumentNullException(nameof(config));
var userInfo = new UTF8Encoding().GetBytes($"{config.User}:{config.Password}");
_httpClient = new HttpClient
{
BaseAddress = new Uri(config.URL),
DefaultRequestHeaders =
{
Accept = { new MediaTypeWithQualityHeaderValue("application/xml")},
Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(userInfo)),
ExpectContinue = false,
},
};
}
public HttpResponseMessage GetHttpResponseMessage()
{
return _httpClient.GetAsync("1234").Result;
}
}
The response I get back appears to go to the correct endpoint; the RequestUri in the response looks exactly like I expect, https://notreallythe.website.com:99/service/1234.
You need to load up Fiddler and do a recording of the HTTP traffic when this operation succeeds (through the browser).
Then, load up your code, stand up another instance (or window) of Fiddler, and do the same thing with your code. Now, compare the two Fiddler windows to see what is different.
You only need to compare those things in Fiddler that are highlighted in blue. You can ignore the other communications.

CancellationToken not working in Asp.Net Web Api 4.5

I've been trying to add a cancellation token to my web api async controller in order to detect when the httpClient times out (so I can stop any pending await calls), spending hours on that without success. Here is my desktop app (.Net 4.0) calling the web api:
public void GetServObjAsync()
{
try
{
var client = new HttpClient()
{
Timeout = TimeSpan.FromMilliseconds(10000)
};
var myUri = new Uri("http://mysWebApi.net/myController");
client.GetAsync(myUri ).ContinueWith(t =>
{
if (t.IsCompleted)
{
t.Result.Content.ReadAsAsync<servObj>().ContinueWith(t2 =>
{
if (!t2.IsFaulted)
{
OnSerVObjReady(t2.Result);
}
}
}
}
}
catch
{
// Handle and notify
}
}
Here is my web api controller:
public class ServController : ApiController
{
public async Task<servObj> Get(CancellationToken Token)
{
try
{
await Task.Delay(15000);
var resp = await singleHttpCallAsync(Token);
if (Token.IsCancellationRequested)
{
return null;
}
var respObj = await multipleHttpCallsAsync(Token);
return respObj;
}
catch
{
return null;
}
}
private Async Task<servObj> multipleHttpCallsAsync(CancellationToken token)
{
var respObjs = new List<detailObj>();
var httpCalls = new Task<detailObj>[2];
httpCalls[0] = singleHttpCall2Async(0, token);
httpCalls[1] = singleHttpCall2Async(1, token);
await Task.WhenAll(httpCalls);
respObjs.Add(await httpCalls[0]);
respObjs.Add(await httpCalls[1]);
return new servObj(respObjs);
}
}
I have tried the approach stated in Client disconnects in ASP.NET Web API, an many others, played with different timeouts, but at the end, the request is complete and servObj(respObjs) is returned. Any help on why the httpClient.timeout is not reflected in my controller would be appreciated.

KeyVault GetSecretAsync never returns

The sample code for working with KeyVault inside a web application has the following code in it:
public static async Task<string> GetSecret(string secretId)
{
var secret = await keyVaultClient.GetSecretAsync(secretId);
return secret.Value;
}
I've incorporated the KeyVaultAccessor object included in the sample in my application in order to test it. The call is executed as part of a query to one of my web api controller methods:
var secret = KeyVaultAccessor.GetSecret("https://superSecretUri").Result;
Unfortunately, the call never returns and the query hangs indefintely...
What might be the reason, because frankly I have no clue where to start...?
This is the common deadlock issue that I describe in full on my blog. In short, the async method is attempting to return to the ASP.NET request context after the await completes, but that request only allows one thread at a time, and there is already a thread in that context (the one blocked on the call to Result). So the task is waiting for the context to be free, and the thread is blocking the context until the task completes: deadlock.
The proper solution is to use await instead of Result:
var secret = await KeyVaultAccessor.GetSecret("https://superSecretUri");
I've used the following code to override the synchronization context:
var secret = Task.Run(async () => await KeyVaultAccessor.GetSecretAsync("https://superSecretUri")).Result;
This still lets you use .Result if you're in a non-async method
Unfortunately, the call never returns and the query hangs indefinitely...
You have a classic deadlock. That's why you shouldn't block on async code. Behind the scenes, the compiler generates a state-machine and captures something called a SynchronizationContext. When you synchronously block the calling thread, the attempt to post the continuation back onto that same context causes the deadlock.
Instead of synchronously blocking with .Result, make your controller async and await on the Task returned from GetSecret:
public async IHttpActionResult FooAsync()
{
var secret = await KeyVaultAccessor.GetSecretAsync("https://superSecretUri");
return Ok();
}
Side note - Async methods should follow naming conventions and be postfixed with Async.
Use the rest api...
public class AzureKeyVaultClient
{
public string GetSecret(string name, string vault)
{
var client = new RestClient($"https://{vault}.vault.azure.net/");
client.Authenticator = new AzureAuthenticator($"https://vault.azure.net");
var request = new RestRequest($"secrets/{name}?api-version=2016-10-01");
request.Method = Method.GET;
var result = client.Execute(request);
if (result.StatusCode != HttpStatusCode.OK)
{
Trace.TraceInformation($"Unable to retrieve {name} from {vault} with response {result.Content}");
var exception = GetKeyVaultErrorFromResponse(result.Content);
throw exception;
}
else
{
return GetValueFromResponse(result.Content);
}
}
public string GetValueFromResponse(string content)
{
var result = content.FromJson<keyvaultresponse>();
return result.value;
}
public Exception GetKeyVaultErrorFromResponse(string content)
{
try
{
var result = content.FromJson<keyvautlerrorresponse>();
var exception = new Exception($"{result.error.code} {result.error.message}");
if(result.error.innererror!=null)
{
var innerException = new Exception($"{result.error.innererror.code} {result.error.innererror.message}");
}
return exception;
}
catch(Exception e)
{
return e;
}
}
class keyvaultresponse
{
public string value { get; set; }
public string contentType { get; set; }
}
class keyvautlerrorresponse
{
public keyvaulterror error {get;set;}
}
class keyvaulterror
{
public string code { get; set; }
public string message { get; set; }
public keyvaulterror innererror { get; set; }
}
class AzureAuthenticator : IAuthenticator
{
private string _authority;
private string _clientId;
private string _clientSecret;
private string _resource;
public AzureAuthenticator(string resource)
{
_authority = WebConfigurationManager.AppSettings["azure:Authority"];
_clientId = WebConfigurationManager.AppSettings["azure:ClientId"];
_clientSecret = WebConfigurationManager.AppSettings["azure:ClientSecret"];
_resource = resource;
}
public AzureAuthenticator(string resource, string tennant, string clientid, string secret)
{
//https://login.microsoftonline.com/<tennant>/oauth2/oken
_authority = authority;
//azure client id (web app or native app
_clientId = clientid;
//azure client secret
_clientSecret = secret;
//vault.azure.net
_resource = resource;
}
public void Authenticate(IRestClient client, IRestRequest request)
{
var token = GetS2SAccessTokenForProdMSA().AccessToken;
//Trace.WriteLine(String.Format("obtained bearer token {0} from ADAL and adding to rest request",token));
request.AddHeader("Authorization", String.Format("Bearer {0}", token));
}
public AuthenticationResult GetS2SAccessTokenForProdMSA()
{
return GetS2SAccessToken(_authority, _resource, _clientId, _clientSecret);
}
private AuthenticationResult GetS2SAccessToken(string authority, string resource, string clientId, string clientSecret)
{
var clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationContext context = new AuthenticationContext(authority, false);
AuthenticationResult authenticationResult = context.AcquireToken(
resource,
clientCredential);
return authenticationResult;
}
}
}
This generic method can be used to override the deadlock issue:
public static T SafeAwaitResult<T>(Func<Task<T>> f)
{
return Task.Run(async () => await f()).Result;
}
Use:
SafeAwaitResult(() => foo(param1, param2));

How to Unit Test method that uses a Webservice on Windows Phone?

I'm working on a project that retrieves information from an external webservice API, but I'm not sure how I'm supposed to test it, I'm quite new at Testing and I have done just a couple of Unit Test, but as far as I know I have to mock the webservice functionality, I've been looking for info regarding this subject but haven't found anything for Windows Phone yet. What's the standard procedure for these type of cases?
Here's a simple version of what I want to test:
public async Task<List<Song>> FetchSongsAsync(String query)
{
if (String.IsNullOrEmpty(query))
return null;
string requestUrl = "webservice url";
var client = new HttpClient();
var result = await client.GetStringAsync(new Uri(requestUrl,UriKind.Absolute));
try
{
var result = JsonConvert.DeserializeObject<RootObject>(result);
return result;
}
catch (Exception)
{
return null;
}
}
Thanks!
Decouple your code from its dependencies: make content loading and its deserialization replaceable:
private readonly IClient client;
private readonly ISerializer serializer;
public YourService(IClient client, ISerializer serializer)
{
_client = client;
_serializer = serializer;
}
public async Task<List<Song>> FetchSongsAsync(String query)
{
try
{
var result = await _client.GetStringAsync(new Uri("http://example.com"));
return _serializer.DeserializeObject<RootObject>(result);
}
catch (Exception)
{
return null;
}
}
The first thing that may help is understand and use dependency injection. Basically taking any dependencies of your object/method/etc and (as it states) injecting them into the object/method/etc. For example, you are having a difficult time figuring out how to test the method because the method depends on being able to access the web service. There are a couple things you can do after this.
One thing to do is to check out mocking frameworks such as Moq.
Another thing I recently did was I added an overloaded constructor (dependency injection) that takes a HttpMessageInvoker object (note HttpClient derives from this). This way I could instantiate the class with my own response message:
public class MyLoader()
{
protected HttpMessageInvoker MessageInvoker { get; set; }
private HttpRequestMessage requestMessage;
public MyLoader() // default constructor
{
MessageInvoker = new HttpClient();
}
public MyLoader(HttpMessageInvoker httpMessageInvoker)
{
MessageInvoker = httpMessageInvoker;
}
public object DoSomething()
{
var response = await MessageInvoker.SendAsync(requestMessage, cancellationTokenSource.Token);
}
Here is my mock message invoker:
public class MockMessageInvoker : HttpMessageInvoker
{
public string ResponseString { get; set; }
public MockMessageInvoker(string responseString)
: base(new HttpClientHandler())
{
ResponseString = responseString;
}
public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
return Task.Run<HttpResponseMessage>(() =>
{
HttpResponseMessage responseMessage = new HttpResponseMessage(
System.Net.HttpStatusCode.OK);
var bytes = Encoding.ASCII.GetBytes(ResponseString);
var stream = new System.IO.MemoryStream(bytes);
responseMessage.Content = new StreamContent(stream);
return responseMessage;
});
}
}
I can call it all like so:
MyLoader loader = new MyLoader(new MockMessageInvoker(validJsonResponse));
loader.DoSomething() // I've removed the dependency on the service and have control of the content in the response
It's quick and dirty, but does the trick.
Hope this helps.

Categories