1

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.
Morse
  • 8,258
  • 7
  • 39
  • 64
  • Why not awaiting both `ReadAsStringAsync` and `ToObjectAsync`? – Aly Elhaddad Jul 23 '18 at 22:22
  • Console applications do not have a synchronization context. Please add the method that calls `GetJson` in your UWP app – Camilo Terevinto Jul 23 '18 at 22:28
  • Tried await on ReadAsStringAsync as well, does not work. GetJson() method has been added within code behind of XAML page, – Morse Jul 23 '18 at 22:29
  • @CamiloTerevinto they [seem to have](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7-1#async-main) included as of C# 7.1. – Aly Elhaddad Jul 23 '18 at 22:34
  • @Prateek this is a working example of the HttpClient that I built into my UWP library and have in a few different published and working apps https://github.com/DotNetRussell/UWPLibrary/blob/master/BasecodeLibrary/BasecodeLibrary/Utilities/BasecodeRequestManager.cs – DotNetRussell Jul 23 '18 at 23:12
  • Possible duplicate of [HttpClient.GetAsync never returns on Xamarin.Android](https://stackoverflow.com/questions/38088542/httpclient-getasync-never-returns-on-xamarin-android) – Camilo Terevinto Jul 24 '18 at 00:43

3 Answers3

2

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.

Aly Elhaddad
  • 1,913
  • 1
  • 15
  • 31
  • The method names have been changed for question context. I had tried making Button_Click as async, there was an error before..let me check and update.. Thanks – Morse Jul 23 '18 at 23:02
  • 1
    This answer is almost correct. The reason why the OP's code doesn't work is because there's a deadlock caused by the context switch between the `await` call and the synchronous block of `.Wait()`, not because of how the result of `GetAsync` is processed – Camilo Terevinto Jul 24 '18 at 00:45
  • @CamiloTerevinto You're right; nice catch! Answer updated. – Aly Elhaddad Jul 24 '18 at 10:36
  • removed `Wait` and made `Button_Click` as `async` Both of the changes were necessary and also `response.StatusCode == System.Net.HttpStatusCode.OK` is good practice. Thanks a lot Aly and Camilo – Morse Jul 24 '18 at 13:44
  • @Prateek you're wrong. You don't have to make the handler async for it to work. The only thing that does is that it makes it asynchronous. As I mentioned earlier, the only thing you needed to do was remove the `Wait()` function call. As Camilo pointed out, you had a deadlock. – DotNetRussell Jul 24 '18 at 14:31
  • I removed Wait..but still I saw same deadlock behaviour. I don't know why – Morse Jul 24 '18 at 14:41
1

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.

DotNetRussell
  • 9,716
  • 10
  • 56
  • 111
  • 2
    This is not true "If you don't make it asnyc though then the request won't be executed.". The call to `SomeClass.GetJson()` will be done and a non-awaited (i.e a fire-and-forget) Task will be created and started. – Camilo Terevinto Jul 24 '18 at 00:40
  • 1
    You're correct, that was a slip when I was typing. Thanks for catching that – DotNetRussell Jul 24 '18 at 01:56
  • The above code is same as mine. `GetJson` is async in mine. Call it to is in static way `Button_Click` is not declared as async – Morse Jul 24 '18 at 13:23
  • @Prateek I'm not sure what you're trying to say. However, The above code was tested by me and does work. – DotNetRussell Jul 24 '18 at 13:30
  • @AnthonyRussell as Camilo told if we dont make it async it wont work, the Button_Click has to be async. I think your library code works but the answer you have posted does not differ any much from my code. – Morse Jul 24 '18 at 13:41
  • @Prateek he was commenting on the fact that it WOULD work. It just runs synchronously. Do what I said in the answer. Create an empty application and paste in what I have. The code DOES work without the handler being marked async. It just doesn't run asynchronously. The compiler even tells you this – DotNetRussell Jul 24 '18 at 13:52
  • and yes it runs asynchronously , dint fetch output. so dint work in my case – Morse Jul 24 '18 at 14:47
1

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.

TheTanic
  • 1,510
  • 15
  • 29
  • Thanks! I did not post `ExchangeRate` to make it [mcve] and yes just making `Button_Click` works – Morse Jul 24 '18 at 13:42
  • No problem. But because it wasnt clear/obvious, if there is a problem with the parsing, it should be in a minimal example – TheTanic Jul 24 '18 at 13:48