1

After reading other answers I can't realize why SendAsync is so slow.

Calling same endpoint from Postman, I got a response in 160ms.

Calling from the code below, takes 10 seconds. I'm using a c# desktop application to make the call.

public static async Task<string> GetToken()
{

    var url = "....";

    var dict = new Dictionary<string, string>();
    dict.Add("username", "foo");
    dict.Add("password", "bar");

    using (var client = new HttpClient(
        new HttpClientHandler
        {
            Proxy = null,
            UseProxy = false
        }))
    {
        //bypass SSL
        ServicePointManager.ServerCertificateValidationCallback = new
            RemoteCertificateValidationCallback
            (
                delegate { return true; }
            );
        var req = new HttpRequestMessage(HttpMethod.Post, url) { Content = new FormUrlEncodedContent(dict) };
        var res = await client.SendAsync(req); //10 seconds here!
        if (res.StatusCode != HttpStatusCode.OK)
            return string.Empty;

        var token = await JsonConvert.DeserializeObject<TokenResponse>(res.Content.ReadAsStringAsync());
        return token.access_token;
    }
}
Beetlejuice
  • 4,292
  • 10
  • 58
  • 84
  • `using (var req = new HttpRequestMessage(...)) { ... }`. [Using objects that implement IDisposable](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/using-objects) – aepot Jul 08 '20 at 19:28
  • 2
    `using (var client = new HttpClient ...` - mistake because [`HttpClient`](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netframework-4.8#examples) is intended to be instantiated once per application, rather than per-use. – aepot Jul 08 '20 at 19:32
  • @aepot you are correct on both points but that would not account for the delay. – Crowcoder Jul 08 '20 at 19:34
  • `FormUrlEncodedContent` is `HttpContent` which is `IDisposable` too. – aepot Jul 08 '20 at 19:36
  • I don't know if this is your problem but you are not following guidelines for when there is no proxy: [To specify that no proxy should be used, set the Proxy property to the proxy instance returned by the GetEmptyWebProxy method.](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.proxy?view=netcore-3.1#remarks) – Crowcoder Jul 08 '20 at 19:36
  • JFYI: `ServicePointManager` doesn't affect `HttpClient` in .NET Core. – aepot Jul 08 '20 at 19:43
  • 1
    @aepot, the problem was to instantiate every request. thanks. – Beetlejuice Jul 08 '20 at 19:44
  • 1
    @aepot, please I will appreciate. – Beetlejuice Jul 08 '20 at 20:05
  • Are you calling it in a loop? – Crowcoder Jul 08 '20 at 20:06
  • @aepot it won't do that for one-off calls, this must be called in a loop or quickly in succession somehow. And, where do you get that ServicePointManager doesn't affect it in .NET Core? – Crowcoder Jul 08 '20 at 20:10
  • @Crowcoder `ServicePointManager` affects `HttpWebRequest`. In .NET Core `HttpClient` doesn't use it ([detailed list by platform here](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netframework-4.8#remarks)). – aepot Jul 08 '20 at 20:17
  • @aepot nice! I guess that explains how ServicePointManager wasn't even included with .NET Core 1.0. – Crowcoder Jul 08 '20 at 20:22

1 Answers1

2

Your code is tangled and ignores IDisposable and this: HttpClient is intended to be instantiated once per application, rather than per-use.

Make reusable method for other-type requests

private static readonly HttpClient client = new HttpClient();

private async Task<T> PostDataAsync<T>(string url, Dictionary<string, string> formData)
{
    using (HttpContent content = new FormUrlEncodedContent(formData))
    using (HttpResponseMessage response = await client.PostAsync(url, content).ConfigureAwait(false))
    {
        response.EnsureSuccessStatusCode(); // throws if 404, 500, etc.
        string responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
        return JsonConvert.DeserializeObject<T>(responseText);
    }
}

Usage

public static async Task<string> GetToken()
{
    var url = "....";

    var dict = new Dictionary<string, string>();
    dict.Add("username", "foo");
    dict.Add("password", "bar");

    try
    {
        TokenResponse token = await PostDataAsync<TokenResponse>(url, dict);
        return token.access_token;
    }
    catch (HttpRequestException ex)
    {
        // handle Exception here
        return string.Empty;
    }
}
aepot
  • 4,558
  • 2
  • 12
  • 24