-1

Introduction

I've been working in ASP.NET Web API where I bumped into some weird interactions when trying to call another API. I've tried 3 different ways to use HttpClient, all with different results.
Have tested everything using Postman, hence some results.

1. Sync HttpClient call

private static string GetAPI(string url)
{
    using (HttpClient client = new HttpClient())
    {
        client.BaseAddress = new Uri(url);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        client.DefaultRequestHeaders.Add("x-api-key", "SomeSecretApiKey");

        HttpResponseMessage response = client.GetAsync(url).Result;
        string contents = response.Content.ReadAsStringAsync().Result;

        if (response.IsSuccessStatusCode)
        {
            return contents;
        }
    }

    return null;
}

Result

Does work, but I want to use async

2. Async httpClient call

private static async Task<string> GetAPI(string url)
{
    using (HttpClient client = new HttpClient())
    {
        client.BaseAddress = new Uri(url);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        client.DefaultRequestHeaders.Add("x-api-key", "SomeSecretApiKey");

        HttpResponseMessage response = await client.GetAsync(url);
        string contents = await response.Content.ReadAsStringAsync();

        if (response.IsSuccessStatusCode)
        {
            return contents;
        }
    }

    return null;
}

Result

Does not work. Will not go beyond the line HttpResponseMessage response = await client.GetAsync(url);, since there's never a response?

3. Async httpClient call with shared HttpClient

private static readonly HttpClient client = new HttpClient();

private static async Task<string> GetAPI(string url)
{
    client.BaseAddress = new Uri(url);
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    client.DefaultRequestHeaders.Add("x-api-key", "SomeSecretApiKey");

    HttpResponseMessage response = await client.GetAsync(url);
    string contents = await response.Content.ReadAsStringAsync();

    if (response.IsSuccessStatusCode)
    {
        return contents;
    }

    return null;
}

Result

Does work once. Will then throw the error: System.AggregateException: One or more errors occurred. ---> System.InvalidOperationException: This instance has already started one or more requests. Properties can only be modified before sending the first request. (as suggested by this SO answer).

Question

I would appreciate it if someone could explain why these results are so different and/or give an alternative way to make a basic HttpClient call (still would like to use async).

Wouter Vanherck
  • 2,070
  • 3
  • 27
  • 41
  • 2
    FYI: Your third example is _not_ a singleton. Its just a shared HttpClient (which is the way you should be using it). Also your third example is not even Async (nor would it compile). You need to return a Task and mark the method as `async`. – maccettura May 23 '18 at 14:36
  • @maccettura Ah thank you for pointing out my mistake, I had made a copy-paste mistake in my third example, I have fixed it now. About the Singleton: that's what the linked answer said and I took it for granted, but will change it right now. Any more on the actual question? – Wouter Vanherck May 23 '18 at 14:47
  • 1
    Example 2 is most likely a common deadlock which occurs when you mix async with synchronous code (ie. not using async all the way). This could be caused by a call to `GetAPI` but using `.Result` instead of `await`. See also releated [An async/await example that causes a deadlock](https://stackoverflow.com/q/15021304/1260204). – Igor May 23 '18 at 14:48
  • 1
    @WouterVanherck Well once you make your third example compile, that looks like every bit of HttpClient code I have written. Nothing stands out as "wrong". You have to worry about DNS issues with a shared HttpClient (it wouldnt respect TTL). – maccettura May 23 '18 at 14:49
  • @Igor I have indeed used `.Result` somewhere around my `GetAPI`-call. Can you point me in the direction of a proper way to fully use async? – Wouter Vanherck May 23 '18 at 14:56
  • 1
    Here's a good article about using the HttpClient: https://blogs.msdn.microsoft.com/henrikn/2012/08/07/httpclient-httpclienthandler-and-webrequesthandler-explained/ – Stevo May 23 '18 at 14:58
  • 1
    Here is a popular article that I found easy to digest when I started working with async / await. https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html – Igor May 23 '18 at 14:59
  • I am not sure what your calling context is though, is it a console application or wpf or asp.net or something else? That will determine how you can start an async call chain. – Igor May 23 '18 at 15:00
  • Steve J, Thank you, I'll check that out | @Igor, I'll check that link out too, since you're probably right about the deadlock. I'm calling the funcion from my API endpoint in ASP.NET Web API – Wouter Vanherck May 23 '18 at 15:08
  • 1
    Then make sure your web api method is also `async` and use `await` all the way down into your `GetAPI` method. – Igor May 24 '18 at 10:12

1 Answers1

0

@Igor has informed me about the deadlock issue in my second example.
So I currently used the HttpClient as such (async):

private static async Task<string> GetAPI(string url)
    {
    // TODO: Use a shared instance of HttpClient
    using (HttpClient client = new HttpClient())
    {
        client.BaseAddress = new Uri(url);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        client.DefaultRequestHeaders.Add("x-api-key", "SomeSecretApiKey");

        var jsonString = await client.GetStringAsync(url).ConfigureAwait(false);
        return jsonString;
    }
}

While some things have been made clear, the entire question hasn't been answered yet. I therefore won't accept this as an answer, yet like to thank those for providing me good information on the use of HttpClient.

Wouter Vanherck
  • 2,070
  • 3
  • 27
  • 41