I am trying to develop my first Xamarin App.
I have two classes for webservices:
RestClient: which makes the request and get the json String
Helper Class: which should get the Json String and deserialized it to the Object Type.
I know that the Wait Method is not the best option, but I have tried so many different suggested versions but it doesn't work. Every try ended in a Deadlock. Every Thread works in the Background. How can I get my Data back to the UI?
code of my RestClient Class:
class RestClient
{
public static string base_url = #"our Restservice address";
// public string completeUrl { get; set; }
HttpClient client;
public RestClient()
{
client = new HttpClient();
client.BaseAddress = new Uri(base_url);
//client.MaxResponseContentBufferSize = 256000;
}
public async Task<String> GetData(string endpoint)
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync(endpoint);
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
return result;
}
else
{
return null;
}
}
Code of my Helper Class
public SupplierHelper()
{
}
public async Task<Suppliers> getData()
{
RestClient restClient = new RestClient();
string result = await restClient.GetData("suppliers/13");
return JsonConvert.DeserializeObject<Suppliers>(result);
}
Code of my VievModelClass
public class AccountViewModel : BaseViewModel
{
public static SupplierHelper supHelper;
public static Suppliers sup;
public string Name { set; get; }
public string Address { set; get; }
public AccountViewModel()
{
loadSupplier().Wait();
}
public async Task loadSupplier()
{
supHelper = new SupplierHelper();
sup = await supHelper.getData();
}
}
Task.Run(loadSupplier).Wait(); will solve your problem.
Your deadlock is caused by async method trying to execute continuation on caller thread, but caller thread is blocked until that async method completes.
.Wait() is worse than "not the best option" - it'll actively cause a circular wait in any environment that has a synchronization context. Read this article for more details.
If you must call this to init the object correctly, you could use an async Factory method or something.
Related
So i'm just trying make an app, push a button and turn off a light. Simple, right? But i'm a dolt.
So i've never played with Web APIs before (or any api, really). I'm following This MS Doc to get a grip on it. Modifying it to make it work but it doesn't (obviously).
In Button_Click the RunUpdate errors 'MainPage.Runupdate()' is a method, which is not valiud in the given context'
This is probably an easy fix but i'm sure to run into more. Anyone up to help me out?
Thanks!
Here's the code:
public sealed partial class MainPage : Page
{
static HttpClient client = new HttpClient();
public class Light
{
//public string ID { get; set }
public string Name { get; set; }
public string state { get; set; }
}
public MainPage()
{
this.InitializeComponent();
client.BaseAddress = new Uri("http://192.168.0.100/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
static async Task<Light> GetLightAsync(string path)
{
Light light = null;
HttpResponseMessage response = await client.GetAsync(path);
if(response.IsSuccessStatusCode)
{
light = await response.Content.ReadAsAsync<Light>();
}
return light;
}
static async Task<Light> UpdateLightAsync(Light light)
{
HttpResponseMessage response = await client.PutAsJsonAsync(
$"api/api/RjplsYoXQvdTl11DOVIo92SKNB7vYRfwZvqCzvDK/lights/1/state/", light.state);
response.EnsureSuccessStatusCode();
light = await response.Content.ReadAsAsync<Light>();
return light;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
RunUpdate.GetAwaiter().GetResult();
}
static async Task RunUpdate()
{
Light light = new Light { Name = "Unknown", state = "off" };
light = await UpdateLightAsync(light);
}
}
Well, RunUpdate() is a method - you forgot parentheses in your RunUpdate call from the Button_Click :)
I am trying get a JSON response from a web API.
I am able to retrive response in Console application with similar code, however in UWP await httpClient.GetAsync(uri); does not work as expected.
public static async Task<double> GetJson()
{
using (var httpClient = new HttpClient())
{
Uri uri= new Uri("https://api.cryptonator.com/api/ticker/btc-usd");
HttpResponseMessage response = await httpClient.GetAsync(uri);
//Below code is not relevent since code is failing on await
var result = response.Content.ReadAsStringAsync();
var jsonResponse = Json.ToObjectAsync<ExchangeRate>(result);//
jsonResponse.Wait();//
ExchangeRate exchangeRateObj = jsonResponse.Result;//
return 1.2;//
}
}
Code behind:
private void Button_Click(object sender,RoutedEventArgs e){
var ROC__ = MyClass.GetJson();
ROC__.Wait();
currency_.ROC = ROC__.Result;
}
What is does not work here means?
It is supposed to connect to URL and fetch the response and response should be assigned some value. Instead on debug with either step into or Continue the control exits the current line skips subsequent lines too.
(I have put debug on next lines too),The app just freezes.
I refereed similar codes for JSON parsing with HTTPClient on Stackoverflow and other blogs , its suggested to use System.Net.Http or Windows.Web.Http
Related Question : how-to-get-a-json-string-from-url
I think tasks are run and its going on forever wait mode , which seems strange as debug mode doesnt show code being run , it just show ready . There is no exception as well.
Am I doing something wrong or missing some Nuget reference?
Please suggest.
PS : Its the same case with httpClient.GetStringAsync method too.
On Console app this line works but not on UWP
var json = new WebClient().DownloadString("https://api.cryptonator.com/api/ticker/btc-usd");
Not duplicate of httpclient-getasync-never-returns-on-xamarin-android
Its not Xamarin , even though it is C# based , my code is different , its not WebApiClient or GetInformationAsync method which I am concerned about.
There're several errors with the code specified that needs to be cured. First, mark your event handler async:
private async void Button_Click(object sender, RoutedEventArgs e)
Second, await GetJson and since this is an asynchronous method, it is a better convention to add the suffix "Async" to it's name; therefore, await GetJsonAsync:
currency_.ROC = await MyClass.GetJsonAsync();
Now, back to GetJsonAsync itself, ReadAsStringAsync and ToObjectAsync should be awaited too:
private static async Task<double> GetJsonAsync()
{
using (var httpClient = new HttpClient())
{
Uri uri = new Uri("https://api.cryptonator.com/api/ticker/btc-usd");
HttpResponseMessage response = await httpClient.GetAsync(uri);
string result = await response.Content.ReadAsStringAsync();
//Below code is not relevent since code is failing on await
ExchangeRate exchangeRateObj = await Json.ToObjectAsync<ExchangeRate>(result);
return 1.2;//
}
}
The reason it wasn't working before is that there was a deadlock caused by the context switch between the await call and the synchronous block of .Wait(). Find more about that here.
Your code works fine. I regenerated it here below. Just get rid of that wonky wait call you're doing.
Do this. Create a new uwp app, paste in the below code and put a breakpoint on the return and see that it gets executed.
It will work regardless if you make your button handler async or not. If you don't make it asnyc though then the request won't be executed asynchronously
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SomeClass.GetJson();
}
}
public class SomeClass
{
public static async Task<double> GetJson()
{
using (var httpClient = new HttpClient())
{
Uri uri = new Uri("https://api.cryptonator.com/api/ticker/btc-usd");
HttpResponseMessage response = await httpClient.GetAsync(uri);
return 1.2;
}
}
}
Might I take this moment for a shameless plug of my UWP lib. It does this work for you.
https://github.com/DotNetRussell/UWPLibrary
In the BasecodeRequestManager.cs file there's a BeginRequest function that does this work async for you. The lib has many many other features as well.
So I tried this by myself. Unfortunately your informations where not complete so a little header:
For the Json-Handling I used Newtonsoft, because I didnt found the Json.ToObjectAsync in my UWP environment.
To create the ExchangeRate- class I used Json2CSharp.
Here are the ExchangeRate classes:
public class ExchangeRate
{
public string Error { get; set; }
public bool Success { get; set; }
public Ticker Ticker { get; set; }
public int Timestamp { get; set; }
}
public class Ticker
{
public string #Base { get; set; }
public string Change { get; set; }
public string Price { get; set; }
public string Target { get; set; }
public string Volume { get; set; }
}
I changed the Button_Click-Method to an async void. Normally its not recommend to have an async void instead of a async Task. But because it is a Handler from a UI-Element its not a problem, because the source will not wait anyway and you shouldn't call this methode directly from your code-behind.
The Button_Click-Method:
private async void Button_Click(object sender, RoutedEventArgs e)
{
var ROC__ = await MyClass.GetJson();
//Do whatever you want with the result.
//Its a double, maybe you want to return an ExchangeRate objcet insted
}
Inside of your GetJson-Method, you need to add an await for your async operations, or add the .Wait() directly after the method and not in a new line. You need to do this, because the Task automatically starts to run, when you call the async operation and the .Wait() comes to late. So your GetJson-Method looks like this:
public static async Task<Double> GetJson()
{
using (var httpClient = new HttpClient())
{
Uri uri = new Uri("https://api.cryptonator.com/api/ticker/btc-usd");
HttpResponseMessage response = await httpClient.GetAsync(uri);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var result = await response.Content.ReadAsStringAsync();
ExchangeRate rate = JsonConvert.DeserializeObject<ExchangeRate>(result); //Newtonsoft
return 1.2;
}
else
{
return -1; //Example value
}
}
}
In addition I added a check, if the Request was successful, to be sure, that we have a response. How I said before: I think you should return a ExchangeRate-object instead of a double, but this depends on your context.
I have X controllers that use a API site (WebApi). I have created an ApiHelper class. Which I use in these controllers. Now my question is this. Can I make this ApiHelper a static class? I think I can because the httpClient is instanced. Or do I overlook something, and does it need to be an instanced ApiHelper. (the use of static still confuses me sometimes). Example code below.
public class HomeController : Controller
{
public async Task<string> VersionDemo()
{
var response = await ApiHelper.Call("/api/config/version");
var data = response.Content.ReadAsStringAsync();
var res = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(data.Result);
return res;
}
}
public class ConfigController : Controller
{
private async Task<List<ConfigSetting>> GetGeneralConfigurationDemo()
{
var generalConfiguration = new List<ConfigSetting>();
var response = await ApiHelper.Call("api/configuration/GetGeneralConfiguration");
var data = response.Content.ReadAsStringAsync();
generalConfiguration = JsonConvert.DeserializeObject<List<ConfigSetting>>(data.Result);
return generalConfiguration;
}
}
public static class ApiHelper
{
public static async Task<HttpResponseMessage> Call(string url)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var baseAdress = System.Configuration.ConfigurationManager.AppSettings["ApiBaseAddress"];
string apiUrl = baseAdress + url;
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(apiUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync(apiUrl);
return response;
}
}
}
Make base controller and hide http client as protected thing.
public abstract class BaseController : Controller
{
protected ApiHelper Api { get; set; }
}
Then derive your controllers from BaseController
public class ConfigController : BaseController {}
public class HomeController : BaseController {}
Note : try not to use static classes cause they make your heap littered. They are allocated in "high-frequency" heap, which is never garbage collected.
There would be no problem to leave your class static as the HttpClient stays on the method scope and thus each call to your static method will use a different HttpClient. It would not be safe if you used a static member (field or property) as it would be shared by all the callers and you would need to synchronize the access (for a multi thread usage).
After reading (httpClient your are doing it wrong , singleton pattern) and subsequently testing. I ended up using the following code. Main goal is one httpClient application wide and avoid socket exhaustion.
In my controllers where I'm in need of a httpClient I use the HttpClientSingleton.Instance see below.
And here is a BaseController you can inherit from in your controllers that are going to use your API.
public class BaseController : Controller
{
public readonly string ApiBaseAdress = System.Configuration.ConfigurationManager.AppSettings["ApiBaseAddress"];
public BaseController()
{
//Set as needed Servicepoint settings
//string SecurityProtocolTypeFromConfig = System.Configuration.ConfigurationManager.AppSettings["SecurityProtocolType"];
//SecurityProtocolType fromConfig;
//Enum.TryParse(SecurityProtocolTypeFromConfig, out fromConfig);
//ServicePointManager.SecurityProtocol = fromConfig;
//possible ServicePoint setting needed in some cases.
//ServicePointManager.Expect100Continue = false;
//ServicePointManager.MaxServicePointIdleTime = 2000;
//ServicePointManager.SetTcpKeepAlive(false, 1, 1);
}
}
And here is the HttpClientSingleton class:
public sealed class HttpClientSingleton
{
private static readonly Lazy<HttpClient> lazy = new Lazy<HttpClient>(() => new HttpClient());
public static HttpClient Instance { get { return lazy.Value; } }
private HttpClientSingleton()
{
}
}
So putting it together. Here is an example of getting some loginfo from the API.
public class MyLogController : BaseController
{
[HttpPost]
public async Task<JsonResult> log(string requestId)
{
var url = ApiBaseAdress + string.Format("/api/runs/log/{0}", requestId);
List<Log> logs = new List<Log>();
var response = await HttpClientSingleton.Instance.GetAsync(url);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
logs = JsonConvert.DeserializeObject<List<Log>>(result);
return Json(logs);
}
}
You can write a static helper class. If the name is ApiHelper, then add a Microsoft.AspNet.WebApi.Client reference. When your app is initialized, call the class's InitializeClient() method, and you can call the GetAsync() method if you need. The code is below:
public static class ApiHelper
{
public static HttpClient ApiClient { get; set; }
public static void InitializeClient()
{
ApiClient = new HttpClient();
ApiClient.DefaultRequestHeaders.Accept.Clear();
ApiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public static async Task<T> GetAsync<T>(string url)
{
using (HttpResponseMessage response = await ApiHelper.ApiClient.GetAsync(url))
{
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<T>();
return result;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
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));
In the code I have await in WallModel method but I can't use async. How can I use async and await in this code?
class WallModel
{
public WallModel()
{
WallItems = new ObservableCollection<Wall>();
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("XXXXX");
string Parse_Text = await response.Content.ReadAsStringAsync();
}
public ObservableCollection<Wall> WallItems { get; set; }
}
Your WallModel method is actually a constructor and it can't be marked async, so you can't use await within it's body either.
In order to use await, method needs to have "async Task" in declaration
async constructors are not allowed.
You have few options like
Use asynchronous factory pattern and don't do initialization in
constructor
Use asynchronous initialization pattern
If you have to go with the first option, your code may look like something like this
class WallModel
{
public WallModel()
{
WallItems = new ObservableCollection<Wall>();
Initialization = InitializeAsyn();
}
public Task Initialization { get; private set; }
private async Taks InitializeAsyn()
{
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("XXXXX");
string Parse_Text = await response.Content.ReadAsStringAsync();
}
public ObservableCollection<Wall> WallItems { get; set; }
}
}
and then somewhere outside of the class
var instance = new WallModel();
await instance.Initialization;
Good article on the subject and how you can do it (as author mentioned in other answer) http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html.